﻿#pragma execution_character_set("utf-8")

#include "pclvisualizer.h"

#include "./ui_pclvisualizer.h"
#include <QColor>
#include <QColorDialog>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QPainter>
#include <QTextList>
#include <QTextStream>

#include <thread>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/common/centroid.h>

bool isRectangularCylinder(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cluster, double aspect_ratio_threshold);
int getChairLeg();
int getTableLeg2();
int getCylinder();
int getCylinder2();
int getCylinder20();

PCLVisualizer::PCLVisualizer(QWidget* parent)
  : QMainWindow(parent)
  , isCloud2(true)            // 设置初始点大小
  , point_size(5)             // 设置初始背景颜色
  , ui(new Ui::PCLVisualizer) //是否默认开启RGBA显示点云
  , isRBGA(true)              // 是否显示第二个点云
  , bgColor(0, 0, 50)         // 初始化顺序要和声明的顺序一致
{
  ui->setupUi(this);
  //  cout << "INIT" << endl;
  initPCV();

  //  //创建动作，工具栏以及菜单栏
  createActions();
  //    createMenus();
  // 创建工具栏
  createToolBars();

  //初始化点云数据
  initPointCloud();

  // 给QVTK配置PCLViewer显示
  viewer_.reset(new pcl::visualization::PCLVisualizer("viewer", false));
  //设置背景颜色
  viewer_->setBackgroundColor(double(bgColor.red()) / 255,
                              double(bgColor.green()) / 255,
                              double(bgColor.blue()) / 255);

  ui->qvtkWidget->SetRenderWindow(viewer_->getRenderWindow());
  viewer_->setupInteractor(ui->qvtkWidget->GetInteractor(),
                           ui->qvtkWidget->GetRenderWindow());
  ui->qvtkWidget->update();
  connectSS();

  // Color the randomly generated cloud
  // 以随机颜色填充点云
  colorCloudDistances();

  if (isRBGA) {
    viewer_->addPointCloud(cloudRGBA_, "cloud");
  } else {
    viewer_->addPointCloud(cloud_, "cloud");
  }
  ////viewer_->addPointCloud(cloud_, "cloud");
  // viewer_->addPointCloud(cloudRGBA_, "cloud");

  viewer_->setPointCloudRenderingProperties(
    pcl::visualization::PCL_VISUALIZER_POINT_SIZE, point_size);
  // viewer_->addCoordinateSystem(1);
  viewer_->resetCamera();
  viewer_->setLookUpTableID("cloud");
  ui->qvtkWidget->update();

  showLogItem("PCV 系统", "系统初始化完成。");
}

PCLVisualizer::~PCLVisualizer()
{
  delete ui;
}

void
PCLVisualizer::initPCV()
{
  //设置窗口名称
  QString str = "PointCloudViewer";
  this->setWindowTitle(str);

  showLogItem("PCV 系统", "系统正在初始化...");

  logStr = "主窗口位置已还原: " + QString("X-Y-Width-Height(%1,%2,%3,%4)")
                                    .arg(this->x())
                                    .arg(this->y())
                                    .arg(this->width())
                                    .arg(this->height());
  showLogItem("PCV 窗口", logStr);

  //写ini文件，记录当前窗口位置和大小：
  QString wstrFilePath =
    qApp->applicationDirPath() + "/setting.ini"; // .ini放在工程源文件目录下
  settings = new QSettings(
    wstrFilePath, QSettings::IniFormat); //用QSetting获取ini文件中的数据
  // settings->clear();                     //清空当前配置文件中的内容
}

void
PCLVisualizer::showLogItem(QString item, QString info)
{
  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + " [ " + item + " ] " + info;
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::saveSetting(QString key, QString value)
{
  //写ini文件，记录当前窗口位置和大小：
  QString wstrFilePath =
    qApp->applicationDirPath() + "/setting.ini"; // .ini放在工程源文件目录下
  QSettings* settings = new QSettings(
    wstrFilePath, QSettings::IniFormat); //用QSetting获取ini文件中的数据
  // settings->clear();                     //清空当前配置文件中的内容
  settings->setValue(key, value);
  settings->sync();
}

QVariant
PCLVisualizer::getSetting(QString name)
{
  return settings->value(name);
}

void
PCLVisualizer::createActions()
{
  //添加点云
  // addCloudAction =
  // new QAction(QIcon(":/images/files/cloud.png"), "添加点云", this);
  // addCloudAction->setShortcut(tr("Ctrl+O"));    //(b)
  // addCloudAction->setStatusTip(tr("添加点云")); //(c)

  //新建工作台
  newWorkStationAction =
    new QAction(QIcon(":/images/files/add.png"), tr("new"), this);
  newWorkStationAction->setShortcut(tr("Ctrl+N"));
  newWorkStationAction->setStatusTip(tr("new"));

  //打开点云文件
  newCloudAction =
    new QAction(QIcon(":/images/files/new2.png"), tr("打开点云文件"), this);
  //  exitAction->setShortcut(tr("Ctrl+Q"));
  newCloudAction->setStatusTip(tr("打开点云文件"));

  //复制点云
  copyCloudAction =
    new QAction(QIcon(":/images/files/copy.png"), tr("复制点云"), this);
  copyCloudAction->setShortcut(tr("Ctrl+C"));
  copyCloudAction->setStatusTip(tr("复制点云"));

  //剪切点云
  cutCloudAction =
    new QAction(QIcon(":/images/files/cut.png"), tr("剪切点云"), this);
  cutCloudAction->setShortcut(tr("Ctrl+X"));
  cutCloudAction->setStatusTip(tr("剪切点云"));

  //粘贴点云
  pasteCloudAction =
    new QAction(QIcon(":/images/files/paste.png"), tr("粘贴点云"), this);
  pasteCloudAction->setShortcut(tr("Ctrl+V"));
  pasteCloudAction->setStatusTip(tr("粘贴点云"));

  //查找点云文件
  searchCloudAction =
    new QAction(QIcon(":/images/files/search.png"), tr("查找点云文件"), this);
  searchCloudAction->setShortcut(tr("Ctrl+F"));
  searchCloudAction->setStatusTip(tr("查找点云文件"));

  //导出点云至PCD文件
  exportCloud2PCDAction = new QAction(
    QIcon(":/images/files/pointCloud.png"), tr("导出点云至PCD文件"), this);
  exportCloud2PCDAction->setStatusTip(tr("导出点云至PCD文件"));

  //导出点云至PLY文件
  exportCloud2PLYAction = new QAction(
    QIcon(":/images/files/cloud2.png"), tr("导出点云至PLY文件"), this);
  exportCloud2PLYAction->setStatusTip(tr("导出点云至PLY文件"));

  //导出点云至CSV文件
  export2CSVAction =
    new QAction(QIcon(":/images/files/CSV.png"), tr("导出点云至CSV文件"), this);
  export2CSVAction->setStatusTip(tr("导出点云至CSV文件"));

  //导出点云至TXT文件
  export2TXTAction =
    new QAction(QIcon(":/images/files/txt.png"), tr("导出点云至TXT文件"), this);
  export2TXTAction->setStatusTip(tr("导出点云至TXT文件"));

  //收藏点云文件
  starCloudAction =
    new QAction(QIcon(":/images/files/star.png"), tr("收藏点云文件"), this);
  starCloudAction->setStatusTip(tr("收藏点云文件"));

  //导出屏幕截图
  snapShotAction =
    new QAction(QIcon(":/images/files/snapshot.png"), tr("导出屏幕截图"), this);
  snapShotAction->setStatusTip(tr("导出屏幕截图"));

  //离群点移除
  outliersRemoveAction = new QAction(
    QIcon(":/images/algorithm/KMeans.png"), tr("outliersRemove"), this);
  outliersRemoveAction->setStatusTip(tr("outliersRemove"));

  //滤波平滑
  filterAction =
    new QAction(QIcon(":/images/algorithm/filter.png"), tr("滤波平滑"), this);
  filterAction->setStatusTip(tr("滤波平滑"));

  //点云下采样
  downSampleAction =
    new QAction(QIcon(":/images/algorithm/density.png"), "downSampling", this);
  downSampleAction->setStatusTip(tr("downSampling"));

  //点云拼接
  cloudSpliceAction =
    new QAction(QIcon(":/images/algorithm/pingjie.png"), "点云拼接", this);
  cloudSpliceAction->setStatusTip(tr("点云拼接"));

  //点云直方图
  HistogramAction =
    new QAction(QIcon(":/images/algorithm/Histogram.png"), "Histogram", this);
  HistogramAction->setStatusTip(tr("Histogram"));

  //表面重建
  surfaceAction =
    new QAction(QIcon(":/images/algorithm/matrix.png"), "surface", this);
  surfaceAction->setStatusTip(tr("surface"));

  //点云配准
  alignAction =
    new QAction(QIcon(":/images/algorithm/DBSCAN.png"), "点云配准", this);
  alignAction->setStatusTip(tr("点云配准"));
  // MLS细化
  MLSAction =
    new QAction(QIcon(":/images/algorithm/nihe.png"), "MLS细化", this);
  MLSAction->setStatusTip(tr("MLS细化"));
}

void
PCLVisualizer::createMenus()
{}

void
PCLVisualizer::createToolBars()
{
  //点云文件工具栏
  fileTool = addToolBar("cloudFile");

  fileTool->addAction(newWorkStationAction);
  // fileTool->addAction(addCloudAction);
  fileTool->addAction(ui->actionload_point_cloud);
  fileTool->addAction(newCloudAction);
  fileTool->addAction(copyCloudAction);
  fileTool->addAction(cutCloudAction);
  fileTool->addAction(pasteCloudAction);

  fileTool->addSeparator();

  fileTool->addAction(exportCloud2PCDAction);
  fileTool->addAction(exportCloud2PLYAction);
  fileTool->addAction(export2CSVAction);
  fileTool->addAction(export2TXTAction);
  fileTool->addAction(ui->actionExportLog);
  fileTool->addAction(snapShotAction);

  fileTool->addSeparator();

  fileTool->addAction(starCloudAction);
  fileTool->addAction(searchCloudAction);
  fileTool->addAction(ui->actionBGColor);

  //算法工具栏
  algorithmTool = addToolBar("algorithm");
  algorithmTool->addAction(ui->actionbestRemoval);
  algorithmTool->addAction(ui->actionbestFiltering);
  algorithmTool->addAction(ui->actionbestKeypoint);
  algorithmTool->addAction(ui->actionbestRegistration);
  algorithmTool->addAction(ui->actionbestSurface);
  algorithmTool->addAction(MLSAction);
  algorithmTool->addAction(HistogramAction);
}

void
PCLVisualizer::test()
{
  qDebug() << "Hello World!";
}

void
PCLVisualizer::initPointCloud()
{
  // Setup the cloud pointer
  cloud_.reset(new PointCloudT);
  cloud.reset(new PointCloudT);
  cloud_noise.reset(new PointCloudT);
  cloud_filtered.reset(new PointCloudT);
  cloud_filtered_guass.reset(new PointCloudT);
  cloud_filtered_guass_down.reset(new PointCloudT);
  cloud_filtered_out.reset(new PointCloudT);

  cloud_in.reset(new PointCloudT);
  cloud_tr.reset(new PointCloudT);
  cloud_RE.reset(new PointCloudT);

  cloudRGBA_.reset(new PointCloudTRGBA);

  // The number of points in the cloud
  cloud_->resize(1000);
  cloudRGBA_->resize(1000);
  // Fill the cloud with random points
  for (size_t i = 0; i < cloud_->points.size(); ++i) {
    cloud_->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
    cloud_->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
    cloud_->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
  }

  // 获取点云内的最大点和最小点
  pcl::getMinMax3D(*cloud_, p_min, p_max);
  maxLen = getMaxValue(p_max, p_min);

  //拷贝一份给RGBA点云
  pcl::copyPointCloud(*cloud_, *cloudRGBA_);

  showLogItem("PCV 主窗口", "点云初始化完成");
}

//连接信号槽
void
PCLVisualizer::connectSS()
{
  connect(ui->pushButton_inc,
          &QPushButton::clicked,
          this,
          &PCLVisualizer::IncPointSize);
  connect(ui->actionload_point_cloud,
          &QAction::triggered,
          this,
          &PCLVisualizer::loadPCDFile);
  connect(ui->actionsave_point_cloud,
          &QAction::triggered,
          this,
          &PCLVisualizer::savePCDFile);
  connect(ui->actionCoordinateSystem,
          &QAction::triggered,
          this,
          &PCLVisualizer::AddCoordinateSystem);
  // Connect "Load" and "Save" buttons and their functions
  connect(ui->pushButton_dec,
          &QPushButton::clicked,
          this,
          &PCLVisualizer::DecPointSize);

  // Connect X,Y,Z radio buttons and their functions
  connect(ui->radioButton_x,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseAxis);
  connect(ui->radioButton_y,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseAxis);
  connect(ui->radioButton_z,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseAxis);

  connect(ui->radioButton_BlueRed,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  connect(ui->radioButton_GreenMagenta,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  connect(ui->radioButton_WhiteRed,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  connect(ui->radioButton_GreyRed,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  connect(ui->radioButton_Rainbow,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  connect(ui->radioButton_others,
          &QRadioButton::clicked,
          this,
          &PCLVisualizer::chooseColorMode);
  //增加新工作台功能
  connect(newWorkStationAction,
          &QAction::triggered,
          this,
          &PCLVisualizer::newWorkStation);
}

void
PCLVisualizer::savePCDFile()
{
  QString filename =
    QFileDialog::getSaveFileName(this,
                                 tr("Open point cloud"),
                                 "/home/",
                                 tr("Point cloud data(*.pcd *.ply)"));
  PCL_INFO("File chosen: %s\n", filename.toStdString().c_str());

  if (filename.isEmpty())
    return;
  int return_status;
  if (filename.endsWith(".pcd", Qt::CaseInsensitive))
    return_status = pcl::io::savePCDFileBinary(filename.toStdString(), *cloud_);
  else if (filename.endsWith(".ply", Qt::CaseInsensitive))
    return_status = pcl::io::savePLYFileBinary(filename.toStdString(), *cloud_);
  else {
    filename.append(".ply");
    return_status = pcl::io::savePLYFileBinary(filename.toStdString(), *cloud_);
  }
  if (return_status != 0) {
    PCL_ERROR("Error writing point cloud %s\n", filename.toStdString().c_str());
    return;
  }
}

void
PCLVisualizer::loadPCDFile()
{  

  QString fileFormat, fileName, fileBaseName, pointCount, filePath, fileSuffix,
    lastPath;
  //读取文件名
  //记住上一次加载的路径

  lastPath = getSetting("FilePath/lastPath").toString();

  QString filePathWithName =
    QFileDialog::getOpenFileName(this,
                                 tr("Open point cloud"),
									lastPath,
                                 tr("Point cloud data (*.pcd *.ply)"));
  QFileInfo fileInfo;
  fileInfo = QFileInfo(filePathWithName);
  //文件名
  fileName = fileInfo.fileName();
  //文件后缀
  fileSuffix = fileInfo.suffix();
  //绝对路径
  filePath = fileInfo.absolutePath();
  fileBaseName = fileInfo.baseName();
   //qDebug() << fileName << endl
   //        << fileSuffix << endl
   //        << filePath << endl
   //        << fileInfo.baseName() << endl
   //        << fileInfo.completeBaseName();

  showLogItem("点云文件选择", filePath);
  // PCL_INFO("File chosen: %s\n", filePathWithName.toStdString().c_str());

  PointCloudT::Ptr cloud_tmp(new PointCloudT);

  if (filePathWithName.isEmpty()) {
    showLogItem("文件加载失败", filePathWithName);
    return;
  }

  showLogItem("文件加载成功", filePathWithName);
  // 将当前文件路径保存，下次使用
  saveSetting("FilePath/lastPath", filePathWithName);

  //判断文件类型然后加载点云
  int return_status;
  if (filePathWithName.endsWith(".pcd", Qt::CaseInsensitive)) {
    return_status =
      pcl::io::loadPCDFile(filePathWithName.toStdString(), *cloud_tmp);
    fileFormat = "PCD";

  } else {
    return_status =
      pcl::io::loadPLYFile(filePathWithName.toStdString(), *cloud_tmp);
    fileFormat = "PLY";
  }

  showLogItem("点云加载中", "文件格式为：" + fileFormat);

  //判断是否加载成功
  if (return_status != 0) {
    PCL_ERROR("Error reading point cloud %s\n",
              filePathWithName.toStdString().c_str());

	showLogItem("点云加载中", fileName+" 文件读取失败。");
    return;
  }
  PCL_INFO("file has loaded\n");

  showLogItem("点云加载完成", fileFormat);
 
  // If point cloud contains NaN values, remove them before updating the
  // visualizer point cloud
  //    True if no points are invalid (e.g., have NaN or Inf values in any of
  //    their floating point fields).

  if (cloud_tmp->is_dense) {
    pcl::copyPointCloud(*cloud_tmp, *cloud_);
  } else {
    PCL_WARN("Cloud is not dense! Non finite points will be removed\n");
    std::vector<int> vec;
    pcl::removeNaNFromPointCloud(*cloud_tmp, *cloud_, vec);
  }
  //将当前点云拷贝给RGBA点云
  pcl::copyPointCloud(*cloud_, *cloudRGBA_);

  showLogItem("点云信息", fileName + QString(" 点云数量: %1").arg(cloud_->points.size()));

  qDebug() << "The number of points :" << cloud_->points.size();


  //更新点云属性信息
  ui->fileFormatEdt->setText(fileFormat);
  ui->fileNameEdt->setText(fileInfo.baseName());
  ui->pointCountEdt->setText(QString("%1").arg(cloud_->points.size()));
  QString cloudFile = fileName + " [" + filePath + "]";
  QListWidgetItem* item = new QListWidgetItem;
  //  item->setBackgroundColor(QColor(220, 230, 250));
  item->setBackground(QBrush(QColor(220, 230, 250)));
  item->setData(Qt::DisplayRole, cloudFile);
  item->setData(Qt::CheckStateRole, Qt::Checked);
  ui->filesList->addItem(item);

  //初始化 A
  for (PointCloudTRGBA::iterator cloud_it = cloudRGBA_->begin();
       cloud_it != cloudRGBA_->end();
       ++cloud_it) {
    //    qDebug() << cloud_it->_PointXYZRGBA::r << " " <<
    //    cloud_it->_PointXYZRGBA::g
    //             << " " << cloud_it->_PointXYZRGBA::b << " "
    //             << cloud_it->_PointXYZRGBA::a;
    cloud_it->_PointXYZRGBA::a = 255;
  }

  pcl::getMinMax3D(*cloud_, p_min, p_max);
  maxLen = getMaxValue(p_max, p_min);

  colorCloudDistances();

  if (isRBGA) {
    viewer_->updatePointCloud(cloudRGBA_, "cloud");
  } else {
    viewer_->updatePointCloud(cloud_, "cloud");
  }
  viewer_->resetCamera();
  viewer_->setPointCloudRenderingProperties(
	  pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1);
  ui->qvtkWidget->update();


  //getTableLeg2();
  getCylinder2();
  //getChairLeg();
  return;
}

void
PCLVisualizer::loadPLYFile()
{
  QString fileFormat, fileName, fileBaseName, pointCount, filePath, fileSuffix;
  //读取文件名
  filePathWithName =
    QFileDialog::getOpenFileName(this,
                                 tr("Open point cloud"),
                                 "E:/BaiduNetdiskWorkspace/Paper-of-Luo/PCD",
                                 tr("Point cloud data (*.pcd *.ply)"));
  QFileInfo fileInfo;
  fileInfo = QFileInfo(filePathWithName);
  //文件名
  fileName = fileInfo.fileName();
  //文件后缀
  fileSuffix = fileInfo.suffix();
  //绝对路径
  filePath = fileInfo.absolutePath();
  fileBaseName = fileInfo.baseName();

  QString cloudFile = fileName + " [" + filePath + "]";
  QListWidgetItem* item = new QListWidgetItem;
  //  item->setBackgroundColor(QColor(220, 230, 250));
  item->setBackground(QBrush(QColor(220, 230, 250)));
  item->setData(Qt::DisplayRole, cloudFile);
  item->setData(Qt::CheckStateRole, Qt::Checked);
  ui->filesList->addItem(item);
}

void
PCLVisualizer::chooseAxis()
{
  if (color_mode_ == 5)
    return;
  // Only 1 of the button can be checked at the time (mutual exclusivity) in a
  // group of radio buttons
  if (ui->radioButton_x->isChecked()) {
    PCL_INFO("x filtering chosen\n");
    filtering_axis_ = 0;
  } else if (ui->radioButton_y->isChecked()) {
    PCL_INFO("y filtering chosen\n");
    filtering_axis_ = 1;
  } else {
    PCL_INFO("z filtering chosen\n");
    filtering_axis_ = 2;
  }

  colorCloudDistances();
  if (isRBGA) {
    viewer_->updatePointCloud(cloudRGBA_, "cloud");
  } else {
    viewer_->updatePointCloud(cloud_, "cloud");
  }
  ui->qvtkWidget->update();
}

void
PCLVisualizer::chooseColorMode()
{
  // Only 1 of the button can be checked at the time (mutual exclusivity) in a
  // group of radio buttons
  if (ui->radioButton_BlueRed->isChecked()) {
    PCL_INFO("Blue -> Red LUT chosen\n");
    color_mode_ = 0;
  } else if (ui->radioButton_GreenMagenta->isChecked()) {
    PCL_INFO("Green -> Magenta LUT chosen\n");
    color_mode_ = 1;
  } else if (ui->radioButton_WhiteRed->isChecked()) {
    PCL_INFO("White -> Red LUT chosen\n");
    color_mode_ = 2;
  } else if (ui->radioButton_GreyRed->isChecked()) {
    PCL_INFO("Grey / Red LUT chosen\n");
    color_mode_ = 3;
  } else if (ui->radioButton_Rainbow->isChecked()) {
    PCL_INFO("Rainbow LUT chosen\n");
    color_mode_ = 4;
  } else {
    PCL_INFO("Full color chosen\n");
    color_mode_ = 5;
    QColor c = QColorDialog::getColor(Qt::red);
    if (c.isValid()) {
      point_color = c;
      qDebug() << "RBG: " << c.red() << " " << c.green() << " " << c.blue();
    }
  }

  colorCloudDistances();
  if (isRBGA) {
    viewer_->updatePointCloud(cloudRGBA_, "cloud");
  } else {
    viewer_->updatePointCloud(cloud_, "cloud");
  }
  ui->qvtkWidget->update();
}

void
PCLVisualizer::IncPointSize()
{
  // setValue函数会自动调用 SpinBox的触发动作
  ui->pointSizeEdt->setValue(++point_size);
}

void
PCLVisualizer::DecPointSize()
{
  if (point_size == 1)
    return;
  ui->pointSizeEdt->setValue(--point_size);
}

void
PCLVisualizer::AddCoordinateSystem()
{
  QString str = ui->actionCoordinateSystem->text();
  if (str.compare("CoordinateSystem [OFF]") == 0) {
    qDebug() << str;
    ui->actionCoordinateSystem->setText("CoordinateSystem [ON]");
    viewer_->addCoordinateSystem();
  } else {
    ui->actionCoordinateSystem->setText("CoordinateSystem [OFF]");
    viewer_->removeCoordinateSystem();
  }
  ui->qvtkWidget->update();
}

//软件关闭时动作
void
PCLVisualizer::closeEvent(QCloseEvent* event)
{
  //写ini文件，记录当前窗口位置和大小：
  QString wstrFilePath =
    qApp->applicationDirPath() + "/setting.ini"; // .ini放在工程源文件目录下
  QSettings* settings = new QSettings(
    wstrFilePath, QSettings::IniFormat); //用QSetting获取ini文件中的数据
  // settings->clear();                     //清空当前配置文件中的内容
  settings->setValue("WindowGeometry/x", this->x());
  settings->setValue("WindowGeometry/y", this->y());
  settings->setValue("WindowGeometry/width", this->width());
  settings->setValue("WindowGeometry/height", this->height());
  qDebug() << "Position is right:" << this->x() << " " << this->y() << " "
           << this->width() << " " << this->height();
}

void
PCLVisualizer::colorCloudDistances()
{

  double min, max;
  switch (filtering_axis_) {
    case 0: // x
      min = cloud_->points[0].x;
      max = cloud_->points[0].x;
      break;
    case 1: // y
      min = cloud_->points[0].y;
      max = cloud_->points[0].y;
      break;
    default: // z
      min = cloud_->points[0].z;
      max = cloud_->points[0].z;
      break;
  }
  // Search for the minimum/maximum
  for (PointCloudTRGBA::iterator cloud_it = cloudRGBA_->begin();
       cloud_it != cloudRGBA_->end();
       ++cloud_it) {
    switch (filtering_axis_) {
      case 0: // x
        if (min > cloud_it->x)
          min = cloud_it->x;

        if (max < cloud_it->x)
          max = cloud_it->x;
        break;
      case 1: // y
        if (min > cloud_it->y)
          min = cloud_it->y;

        if (max < cloud_it->y)
          max = cloud_it->y;
        break;
      default: // z
        if (min > cloud_it->z)
          min = cloud_it->z;

        if (max < cloud_it->z)
          max = cloud_it->z;
        break;
    }
  }
  // Compute LUT scaling to fit the full histogram spectrum
  double lut_scale = 255.0 / (max - min); // max is 255, min is 0

  if (min ==
      max) // In case the cloud is flat on the chosen direction (x,y or z)
    lut_scale = 1.0; // Avoid rounding error in boost

  for (PointCloudTRGBA::iterator cloud_it = cloudRGBA_->begin();
       cloud_it != cloudRGBA_->end();
       ++cloud_it) {
    int value;
    switch (filtering_axis_) {
      case 0: // x
        value = boost::math::iround(
          (cloud_it->x - min) *
          lut_scale); // Round the number to the closest integer
        break;
      case 1: // y
        value = boost::math::iround((cloud_it->y - min) * lut_scale);
        break;
      default: // z
        value = boost::math::iround((cloud_it->z - min) * lut_scale);
        break;
    }

    // Apply color to the cloud
    switch (color_mode_) {
      case 0:
        // Blue (= min) -> Red (= max)
        cloud_it->r = value;
        cloud_it->g = 0;
        cloud_it->b = 255 - value;
        break;
      case 1:
        // Green (= min) -> Magenta (= max)
        cloud_it->r = value;
        cloud_it->g = 255 - value;
        cloud_it->b = value;
        break;
      case 2:
        // White (= min) -> Red (= max)
        cloud_it->r = 255;
        cloud_it->g = 255 - value;
        cloud_it->b = 255 - value;
        break;
      case 3:
        // Grey (< 128) / Red (> 128)
        if (value > 128) {
          cloud_it->r = 255;
          cloud_it->g = 0;
          cloud_it->b = 0;
        } else {
          cloud_it->r = 128;
          cloud_it->g = 128;
          cloud_it->b = 128;
        }
        break;
      case 5:
        cloud_it->r = point_color.red();
        cloud_it->g = point_color.green();
        cloud_it->b = point_color.blue();
        break;
      default:
        // Blue -> Green -> Red (~ rainbow)
        cloud_it->r =
          value > 128 ? (value - 128) * 2 : 0; // r[128] = 0, r[255] = 255
        cloud_it->g =
          value < 128
            ? 2 * value
            : 255 - ((value - 128) * 2); // g[0] = 0, g[128] = 255, g[255] = 0
        cloud_it->b =
          value < 128 ? 255 - (2 * value) : 0; // b[0] = 255, b[128] = 0
    }
  }
}

void
PCLVisualizer::on_actionUp_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(0.5 * (p_min.x + p_max.x),
                               0.5 * (p_min.y + p_max.y),
                               p_max.z + 2 * maxLen,
                               0.5 * (p_min.x + p_max.x),
                               0.5 * (p_min.y + p_max.y),
                               p_max.z,
                               0,
                               1,
                               0);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionBottom_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(0.5 * (p_min.x + p_max.x),
                               0.5 * (p_min.y + p_max.y),
                               p_min.z - 2 * maxLen,
                               0.5 * (p_min.x + p_max.x),
                               0.5 * (p_min.y + p_max.y),
                               p_min.z,
                               0,
                               1,
                               0);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionFront_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(0.5 * (p_min.x + p_max.x),
                               p_min.y - 2 * maxLen,
                               0.5 * (p_min.z + p_max.z),
                               0.5 * (p_min.x + p_max.x),
                               p_min.y,
                               0.5 * (p_min.z + p_max.z),
                               0,
                               0,
                               1);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionBack_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(0.5 * (p_min.x + p_max.x),
                               p_max.y + 2 * maxLen,
                               0.5 * (p_min.z + p_max.z),
                               0.5 * (p_min.x + p_max.x),
                               p_min.y,
                               0.5 * (p_min.z + p_max.z),
                               0,
                               0,
                               1);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionLeft_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(p_min.x - 2 * maxLen,
                               0.5 * (p_min.y + p_max.y),
                               0.5 * (p_min.z + p_max.z),
                               p_max.x,
                               0.5 * (p_min.y + p_max.y),
                               0.5 * (p_min.z + p_max.z),
                               0,
                               0,
                               1);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionRight_triggered()
{
  if (!cloud_->empty()) {
    viewer_->setCameraPosition(p_max.x + 2 * maxLen,
                               0.5 * (p_min.y + p_max.y),
                               0.5 * (p_min.z + p_max.z),
                               p_max.x,
                               0.5 * (p_min.y + p_max.y),
                               0.5 * (p_min.z + p_max.z),
                               0,
                               0,
                               1);
    ui->qvtkWidget->update();
  }
}

double
PCLVisualizer::getMinValue(PointT p1, PointT p2)
{
  double min = 0;

  if (p1.x - p2.x > p1.y - p2.y) {
    min = p1.y - p2.y;
  } else {
    min = p1.x - p2.x;
  }

  if (min > p1.z - p2.z) {
    min = p1.z - p2.z;
  }
  return min;
}

double
PCLVisualizer::getMaxValue(PointT p1, PointT p2)
{
  double max = 0;

  if (p1.x - p2.x > p1.y - p2.y) {
    max = p1.x - p2.x;

  } else {
    max = p1.y - p2.y;
  }

  if (max < p1.z - p2.z) {
    max = p1.z - p2.z;
  }

  return max;
}

void
PCLVisualizer::openProgressDlg(int num = 500)
{
  //创建一个进度对话框
  QProgressDialog* progressDialog = new QProgressDialog(this);
  QFont font("ZYSong18030", 12);
  progressDialog->setFont(font);
  progressDialog->setWindowModality(Qt::WindowModal); //(d)
  progressDialog->setMinimumDuration(5);              //(e)
  progressDialog->setWindowTitle(tr("Please Wait"));  //(f)
  progressDialog->setLabelText(tr("Processing..."));  //(g)
  progressDialog->setCancelButtonText(tr("Cancel"));  //(h)
  progressDialog->setRange(0, num); //设置进度对话框的步进范围
  for (int i = 1; i < num + 1; i++) {
    Sleep(10);
    progressDialog->setValue(i);       //(i)
    if (progressDialog->wasCanceled()) //(j)
      return;
  }
}

void
PCLVisualizer::updateCloudInfo()
{
  // TODO 暂时只更新点云数量
  ui->pointCountEdt->setText(QString::number(cloud_->points.size()));
}

void
PCLVisualizer::best_filter()
{
  pcl::console::TicToc time;

  //创建一个进度对话框
  QProgressDialog* progressDialog = new QProgressDialog(this);
  QFont font("ZYSong18030", 12);
  progressDialog->setFont(font);
  progressDialog->setWindowModality(Qt::WindowModal); //(d)
  progressDialog->setMinimumDuration(10);             //(e)
  progressDialog->setWindowTitle(tr("Please Wait"));  //(f)
  progressDialog->setLabelText(tr("Processing..."));  //(g)
  progressDialog->setCancelButtonText(tr("Cancel"));  //(h)
  progressDialog->setRange(0, 100); //设置进度对话框的步进范围

  progressDialog->setValue(10);      //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;

  *cloud = *cloud_;
  //添加高斯噪声
  time.tic();
  GenerateGaussNoise(cloud, cloud_noise, 0, 0.001);
  std::cout << "添加高斯噪声耗时: " << time.toc() << " ms" << std::endl;
  std::cerr << "添加高斯噪声: " << std::endl;
  std::cerr << *cloud_noise << std::endl;
  // show_point_cloud(cloud_noise, "point cloud with noise");

  progressDialog->setValue(20);      //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;
  //离群点移除
  time.tic();
  radius_filter(cloud_noise, cloud_filtered, cloud_filtered_out);
  // statistical_filter(cloud_noise, cloud_filtered, cloud_filtered_out);
  // std::cout << "离群点移除耗时: " << time.toc() << " ms" << std::endl;
  // std::cerr << "离群点移除: " << std::endl;
  // std::cerr << *cloud_filtered << std::endl;

  //--------------------LOG--------------------------
  logStr =
    "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "] " +
    QString("[离群点移除] Outliers Remove use time : %1").arg(time.toc()) +
    " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  progressDialog->setValue(40);      //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;

  //高斯平滑
  time.tic();
  gaussian_filter(cloud_filtered, cloud_filtered_guass, 0.01);
  // std::cout << "高斯平滑耗时: " << time.toc() << " ms" << std::endl;
  // std::cerr << "高斯平滑: " << std::endl;
  // std::cerr << *cloud_filtered_guass << std::endl;

  //--------------------LOG--------------------------
  logStr =
    "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "] " +
    QString("[滤波平滑] Filtering use time : %1").arg(time.toc()) + " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  progressDialog->setValue(60);      //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;

  //下采样
  time.tic();
  downSampling(cloud_filtered_guass, cloud_filtered_guass_down, 0.01);
  // std::cout << "下采样耗时: " << time.toc() << " ms" << std::endl;
  // std::cerr << "下采样: " << std::endl;
  // std::cerr << *cloud_filtered_guass_down << std::endl;

  //--------------------LOG--------------------------
  logStr =
    "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "] " +
    QString("[下采样] Down Sampling use time : %1").arg(time.toc()) + " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  progressDialog->setValue(80);      //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;

  writer.write<pcl::PointXYZ>("cloud_noise.pcd", *cloud_noise, false);
  writer.write<pcl::PointXYZ>("cloud_filtered.pcd", *cloud_filtered, false);
  writer.write<pcl::PointXYZ>(
    "cloud_filtered_guass.pcd", *cloud_filtered_guass, false);
  writer.write<pcl::PointXYZ>(
    "cloud_filtered_guass_down.pcd", *cloud_filtered_guass_down, false);

  // text_vector.push_back("cloud");
  // text_vector.push_back("cloud_noise");
  // text_vector.push_back("cloud_filtered");
  // text_vector.push_back("cloud_filtered_guass");
  // text_vector.push_back("cloud_filtered_guass_down");
  // cloudPtr_vector.push_back(cloud);
  // cloudPtr_vector.push_back(cloud_noise);
  // cloudPtr_vector.push_back(cloud_filtered);
  // cloudPtr_vector.push_back(cloud_filtered_guass);
  // cloudPtr_vector.push_back(cloud_filtered_guass_down);

  // TODO RGB点云暂不更新

  *cloud_ = *cloud_filtered_guass_down;
  // if (isRBGA) {
  //	viewer_->updatePointCloud(cloudRGBA_, "cloud");
  //}
  // else {
  //	viewer_->updatePointCloud(cloud_, "cloud");
  //}
  viewer_->updatePointCloud(cloud_, "cloud");
  viewer_->resetCamera();
  ui->qvtkWidget->update();
  //更新点云信息
  updateCloudInfo();

  progressDialog->setValue(100);     //(i)
  if (progressDialog->wasCanceled()) //(j)
    return;

  return;
}

void
PCLVisualizer::cloudTransform(PointCloudT::Ptr cloud_in,
                              PointCloudT::Ptr cloud_tr,
                              double theta,
                              double z)
{
  //设置旋转矩阵和平移向量
  Eigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity();

  //旋转角度
  // double theta = M_PI / 4;  // The angle of rotation in radians
  transformation_matrix(0, 0) = cos(theta);
  transformation_matrix(0, 1) = -sin(theta);
  transformation_matrix(1, 0) = sin(theta);
  transformation_matrix(1, 1) = cos(theta);

  double x = ui->lineEdit_X->text().toDouble();
  double y = ui->lineEdit_Y->text().toDouble();
  double zz = ui->lineEdit_Z->text().toDouble();
  qDebug() << "x:" << x << " y:" << y << " z:" << zz << endl;
  transformation_matrix(0, 3) = x;  // 0.1m
  transformation_matrix(1, 3) = y;  // 0.1m
  transformation_matrix(2, 3) = zz; // 0.1m
                                    // transformation_matrix(2, 3) = z; // 0.1m
  // Z轴方向上的平移
  // A translation on Z axis (0.4 meters)
  // transformation_matrix(2, 3) = z; // 0.1m

  // Display in terminal the transformation matrix
  // std::cout << "对源点云进行旋转平移：" << std::endl;
  print4x4Matrix(transformation_matrix);

  //将原始点云先旋转平移
  // Executing the transformation
  pcl::transformPointCloud(*cloud_in, *cloud_tr, transformation_matrix);
}

void
PCLVisualizer::ICP_aligin(pcl::IterativeClosestPoint<PointT, PointT>::Ptr icp,
                          PointCloudT::Ptr cloud_in,
                          PointCloudT::Ptr cloud_RE)
{
  icp->setMaximumIterations(1);
  icp->setInputSource(cloud_RE);
  icp->setInputTarget(cloud_in);
  icp->align(*cloud_RE);
  icp->setMaximumIterations(1); // We set this variable to 1 for the next time
                                // we will call .align () function
  std::cout << "Applied " << iterations << " iteration(s)" << std::endl;
  if (icp->hasConverged()) {
    std::cout << "\nHasConverged: " << icp->hasConverged()
              << ", getFitnessScore: " << icp->getFitnessScore() << std::endl;
    cout << "Transformation: \n" << icp->getFinalTransformation() << endl;
    print4x4Matrix(icp->getFinalTransformation().cast<double>());
    qDebug() << get4x4MatrixStr(icp->getFinalTransformation().cast<double>())
             << endl;
    ////ui->matEdit.setText(get4x4MatrixStr(icp->getFinalTransformation().cast<double>()));
    ui->matEdit->setPlainText(
      get4x4MatrixStr(icp->getFinalTransformation().cast<double>()));
  } else {
    PCL_ERROR("\nICP has not converged.\n");
    // return (-1);
  }
}

void
PCLVisualizer::best_aligin()
{
  icp = boost::make_shared<pcl::IterativeClosestPoint<PointT, PointT>>();

  *cloud_in = *cloud_;
  //将原始点云旋转平移
  cloudTransform(cloud_in, cloud_RE, 0, 10);
  *cloud_tr = *cloud_RE; // 在cloud_tr中备份，以供显示

  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h(
    cloud_in, 20, 20, 180);
  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h(
    cloud_tr, 250, 80, 0);
  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h(
    cloud_RE, 180, 20, 20);
  if (isCloud2) {
    viewer_->addPointCloud(cloud_tr, cloud_tr_color_h, "cloud2");
    isCloud2 = false;
  } else {
    viewer_->updatePointCloud(cloud_tr, cloud_tr_color_h, "cloud2");
  }

  viewer_->updatePointCloud(cloud_in, cloud_in_color_h, "cloud");
  viewer_->resetCamera();
  ui->qvtkWidget->update();
}

void
PCLVisualizer::best_surface()
{
  *cloud_in = *cloud_;
  pcl::PolygonMesh mesh;     //存储最终三角化的网格模型
  pcl::PolygonMesh mesh_mls; //存储最终三角化的网格模型

  //下采样精简点云
  Voxel_downsampling(cloud_in, cloud, 0);

  //-----------------连接XYZ和法向量字段--------------
  pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(
    new pcl::PointCloud<pcl::PointNormal>);
  pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals_mls(
    new pcl::PointCloud<pcl::PointNormal>);

  bool isMLS = false;

  if (isMLS) {
    MLS(cloud, cloud_with_normals_mls);
    GP(cloud, cloud_with_normals_mls, mesh);
  } else {
    //----------------法线估计-------------------------
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> n;
    pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(
      new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(cloud);
    n.setInputCloud(cloud);
    n.setSearchMethod(tree);
    n.setKSearch(10);
    n.compute(*normals);

    pcl::concatenateFields(*cloud, *normals, *cloud_with_normals);
    GP(cloud, cloud_with_normals, mesh);
  }
  viewer_->removePointCloud("cloud");
  viewer_->removePointCloud("cloud2");
  viewer_->addPolygonMesh(mesh, "mesh");
  viewer_->resetCamera();
  ui->qvtkWidget->update();
}

void
PCLVisualizer::newWorkStation()
{
  PCLVisualizer* newPCV = new PCLVisualizer;
  //新建的工作窗口位于之前窗口的右下方
  newPCV->setGeometry(
    this->x() + 20, this->y() + 50, this->width(), this->height());
  newPCV->show();
}

void
PCLVisualizer::on_actionBGColor_triggered()
{
  QColor color = QColorDialog::getColor(
    bgColor, this); //打开颜色选择窗口，并用当前颜色初始化
  if (color.isValid()) {
    bgColor = color;
    qDebug() << "color: " << bgColor.red() << " " << bgColor.green() << " "
             << bgColor.blue();
    viewer_->setBackgroundColor(double(bgColor.red()) / 255,
                                double(bgColor.green()) / 255,
                                double(bgColor.blue()) / 255);
    ui->qvtkWidget->update();
  }
}

void
PCLVisualizer::on_actionabout_triggered()
{}

void
PCLVisualizer::on_comboBox_Color_currentIndexChanged(const QString& arg1)
{

  isRBGA = (arg1 == "RGB");
  if (isRBGA) {
    viewer_->updatePointCloud(cloudRGBA_, "cloud");
  } else {
    viewer_->updatePointCloud(cloud_, "cloud");
  }
  ui->qvtkWidget->update();
  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + QString("[点云颜色模式] Change to %1").arg(arg1);
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionCoordinateSystem_triggered()
{}

void
PCLVisualizer::on_actionCameraview_triggered()
{}

void
PCLVisualizer::on_pointSizeEdt_valueChanged(int arg1)
{
  point_size = arg1;
  ui->pointSizeEdt->setValue(arg1);
  viewer_->setPointCloudRenderingProperties(
    pcl::visualization::PCL_VISUALIZER_POINT_SIZE, point_size);
  ui->label_pointSize->setText(QString::number(point_size));
  qDebug() << "point_size = " << point_size;
  ui->qvtkWidget->update();

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + QString("[数据点大小] POINT_SIZE: %1").arg(arg1);
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionbestSurface_triggered()
{

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[表面重建] surface rebuild start";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  q_time.start();

  openProgressDlg(300);
  best_surface();

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[表面重建] surface rebuild Done";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr =
    "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "] " +
    QString("[表面重建] surface rebuild use time : %1").arg(q_time.elapsed()) +
    " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionbestRemoval_triggered()
{

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[去噪平滑] Outliers Remove start";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  q_time.start();
  // openProgressDlg(300);
  best_filter();

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[去噪平滑] Outliers Remove Done";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr =
    "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + "] " +
    QString("[去噪平滑] Outliers Remove use time : %1").arg(q_time.elapsed()) +
    " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionbestFiltering_triggered()
{

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[滤波平滑] surface filtering start";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  q_time.start();
  openProgressDlg(300);
  Voxel_downsampling(cloud_, cloud_, 0.001);

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[滤波平滑] surface filtering Done";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " +
           QString("[滤波平滑] surface filtering use time : %1")
             .arg(q_time.elapsed()) +
           " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  updateCloudInfo();
  viewer_->updatePointCloud(cloud_, "cloud");
  viewer_->resetCamera();
  ui->qvtkWidget->update();
}

void
PCLVisualizer::on_actionbestRegistration_triggered()
{

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[点云配准] Cloud Registration start";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
  q_time.start();
  // openProgressDlg(300);
  best_aligin();

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[点云配准] Cloud Registration Done";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " +
           QString("[点云配准] Cloud Registration use time : %1")
             .arg(q_time.elapsed()) +
           " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionbestKeypoint_triggered()
{

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[关键点提取] Key points extracting start";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  q_time.start();
  openProgressDlg(300);
  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[关键点提取] Key points extracting Done";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " +
           QString("[关键点提取] Key points extracting use time : %1")
             .arg(q_time.elapsed()) +
           " ms";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------
}

void
PCLVisualizer::on_actionTXT_triggered()
{}


int getCylinder()
{
    const std::string filename = "D:/temp/data/tutorials/table_scene_lms400.pcd";
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr table(new pcl::PointCloud<pcl::PointXYZ>);


    // 加载点云数据
    if (pcl::io::loadPCDFile<pcl::PointXYZ>(filename, *cloud) == -1)
    {
        std::cerr << "Failed to load PCD file!" << std::endl;
        return -1;
    }

    // 1. 降采样
    pcl::VoxelGrid<pcl::PointXYZ> vg;
    vg.setInputCloud(cloud);
    vg.setLeafSize(0.01f, 0.01f, 0.01f);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    vg.filter(*cloud_filtered);

    // 2. 分割 - 示例使用欧式聚类
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(cloud_filtered);
    std::vector<pcl::PointIndices> cluster_indices;
    pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
    ec.setClusterTolerance(0.02); // 两点之间的最大距离
    ec.setMinClusterSize(100);
    ec.setMaxClusterSize(25000);
    ec.setSearchMethod(tree);
    ec.setInputCloud(cloud_filtered);
    ec.extract(cluster_indices);


    // 可视化
    pcl::visualization::PCLVisualizer viewer("Table Leg Detection");
    viewer.setBackgroundColor(0, 0, 0);
    viewer.addPointCloud(cloud, "original_cloud");
    //viewer.addPointCloud(table, "table_cloud");

    // 对每个簇进行进一步分析，判断是否为矩形柱体并绘制中心线
    for (const auto& indices : cluster_indices) {
        pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
        for (const int idx : indices.indices) {
            cluster->push_back((*cloud_filtered)[idx]);
        }

        // 这里需要自定义逻辑来判断是否为矩形柱体
        if (!isRectangularCylinder(cluster, 10)) {
            continue;
        }

        // 假设我们已经识别出一个矩形柱体，计算并绘制中心线
        Eigen::Vector4f minPt, maxPt;
        pcl::getMinMax3D(*cluster, minPt, maxPt);
        Eigen::Vector3f center = 0.5 * (minPt.head<3>() + maxPt.head<3>()); // 简化的中心点计算


        // 使用之前提到的 addLine 方法绘制中心线，但需要注意的是，这里只是示例，
        // 实际上还需要知道线的两端点，这取决于您如何定义“中心线”。
        // 假设我们简单地以中心点为起点，沿着Z轴向上绘制一条线作为示例
        viewer.addLine(pcl::PointXYZ(center[0], center[1], center[2]), // 起点
            pcl::PointXYZ(center[0], center[1], center[2] + 0.1), // 简单假设的终点
            1.0, 0.0, 0.0, "center_line", 0);
    }

    // 最后记得启动PCLVisualizer并保持它运行
    while (!viewer.wasStopped()) {
        viewer.spinOnce(100);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return 0;
}


bool isRectangularCylinder(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cluster, double aspect_ratio_threshold) {
    return true;
    // 初始化矩形柱体特性估计器
    pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
    feature_extractor.setInputCloud(cluster);

    
    // 计算惯性矩和OBB
    feature_extractor.compute();

    // 手动计算质心
    Eigen::Vector4f centroid;
    pcl::compute3DCentroid(*cluster, centroid);
    Eigen::Vector3f position_OBB(centroid.head<3>()); // 只取前三维作为质心位置

    // 手动计算最小点和最大点
    Eigen::Vector3f min_point_OBB(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
    Eigen::Vector3f max_point_OBB(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());

    for (const auto& point : *cluster) {
        min_point_OBB.x() = std::min(min_point_OBB.x(), point.x);
        min_point_OBB.y() = std::min(min_point_OBB.y(), point.y);
        min_point_OBB.z() = std::min(min_point_OBB.z(), point.z);

        max_point_OBB.x() = std::max(max_point_OBB.x(), point.x);
        max_point_OBB.y() = std::max(max_point_OBB.y(), point.y);
        max_point_OBB.z() = std::max(max_point_OBB.z(), point.z);
    }

    // 计算长宽高
    double length = std::max(max_point_OBB.x() - min_point_OBB.x(), max_point_OBB.y() - min_point_OBB.y());
    double width = std::min(max_point_OBB.x() - min_point_OBB.x(), max_point_OBB.y() - min_point_OBB.y());
    double height = max_point_OBB.z() - min_point_OBB.z();

    // 判断长宽比
    if (std::max(length, width) / std::min(length, width) > aspect_ratio_threshold) {
        // 如果长宽比超过阈值，认为可能是矩形柱体
        return true;
    }

    return false;
}
/*
bool isRectangularCylinder(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cluster, double aspect_ratio_threshold) {
    // 可选：预处理，比如去除离群点，提高拟合精度
    pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
    sor.setInputCloud(cluster);
    sor.setMeanK(50);
    sor.setStddevMulThresh(1.0);
    sor.filter(*cluster);

    // 计算点云的Oriented Bounding Box (OBB)
    pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
    feature_extractor.setInputCloud(cluster);
    feature_extractor.compute();

    Eigen::Vector3f min_point_OBB, max_point_OBB, position_OBB;
    Eigen::Matrix3f rotational_matrix_OBB;
    feature_extractor.getOBB(min_point_OBB, max_point_OBB, position_OBB, rotational_matrix_OBB);

    // 获取长宽高
    double length = std::max(max_point_OBB.x() - min_point_OBB.x(), max_point_OBB.y() - min_point_OBB.y());
    double width = std::min(max_point_OBB.x() - min_point_OBB.x(), max_point_OBB.y() - min_point_OBB.y());
    double height = max_point_OBB.z() - min_point_OBB.z();

    // 判断长宽比
    if (std::max(length, width) / std::min(length, width) > aspect_ratio_threshold) {
        // 如果长宽比超过阈值，认为可能是矩形柱体
        return true;
    }

    return false;
}*/

int getTableLeg2()
{
    // Read in the cloud data
    pcl::PCDReader reader;
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>), cloud_f(new pcl::PointCloud<pcl::PointXYZ>);
    reader.read("D:/temp/data/tutorials/table_scene_lms400.pcd", *cloud);
    std::cout << "PointCloud before filtering has: " << cloud->size() << " data points." << std::endl; //*

    // Create the filtering object: downsample the dataset using a leaf size of 1cm
    pcl::VoxelGrid<pcl::PointXYZ> vg;
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    vg.setInputCloud(cloud);
    vg.setLeafSize(0.01f, 0.01f, 0.01f);
    vg.filter(*cloud_filtered);
    std::cout << "PointCloud after filtering has: " << cloud_filtered->size() << " data points." << std::endl; //*

    // Create the segmentation object for the planar model and set all the parameters
    pcl::SACSegmentation<pcl::PointXYZ> seg;
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZ>());
    pcl::PCDWriter writer;
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_PLANE);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(100);
    seg.setDistanceThreshold(0.02);

    int nr_points = (int)cloud_filtered->size();
    while (cloud_filtered->size() > 0.3 * nr_points)
    {
        // Segment the largest planar component from the remaining cloud
        seg.setInputCloud(cloud_filtered);
        seg.segment(*inliers, *coefficients);
        if (inliers->indices.size() == 0)
        {
            std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
            break;
        }

        // Extract the planar inliers from the input cloud
        pcl::ExtractIndices<pcl::PointXYZ> extract;
        extract.setInputCloud(cloud_filtered);
        extract.setIndices(inliers);
        extract.setNegative(false);

        // Get the points associated with the planar surface
        extract.filter(*cloud_plane);
        std::cout << "PointCloud representing the planar component: " << cloud_plane->size() << " data points." << std::endl;

        // Remove the planar inliers, extract the rest
        extract.setNegative(true);
        extract.filter(*cloud_f);
        *cloud_filtered = *cloud_f;
    }

    // Creating the KdTree object for the search method of the extraction
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(cloud_filtered);

    std::vector<pcl::PointIndices> cluster_indices;
    pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
    ec.setClusterTolerance(0.02); // 2cm
    ec.setMinClusterSize(100);
    ec.setMaxClusterSize(25000);
    ec.setSearchMethod(tree);
    ec.setInputCloud(cloud_filtered);
    ec.extract(cluster_indices);

    pcl::visualization::PCLVisualizer viewer("Cylinder Detection");
    int j = 0;
    for (const auto& cluster : cluster_indices)
    {      
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster(new pcl::PointCloud<pcl::PointXYZ>);
        for (const auto& idx : cluster.indices) {
            cloud_cluster->push_back((*cloud_filtered)[idx]);
        } //*
        cloud_cluster->width = cloud_cluster->size();
        cloud_cluster->height = 1;
        cloud_cluster->is_dense = true;

        std::cout << "PointCloud representing the Cluster: " << cloud_cluster->size() << " data points." << std::endl;
        std::stringstream ss;
        ss << std::setw(4) << std::setfill('0') << j;
        //writer.write<pcl::PointXYZ>("cloud_cluster_" + ss.str() + ".pcd", *cloud_cluster, false); //*
        j++;

        
        // 这里需要自定义逻辑来判断是否为矩形柱体
        if (isRectangularCylinder(cloud_cluster, 10)) {
            pcl::PointXYZRGB cluster_centroid;
            //pcl::compute3DCentroid(*cloud_cluster, cluster_centroid);

            // 获取点云的边界框
            pcl::PointXYZ min_pt, max_pt;
            pcl::getMinMax3D(*cloud_cluster, min_pt, max_pt);

            
            // 计算线段长度，这里以最大边长作为参考，您也可以根据实际情况调整
            double line_length = std::max(max_pt.x - min_pt.x, std::max(max_pt.y - min_pt.y, max_pt.z - min_pt.z));

            // 绘制中心线：从质心出发，沿主方向绘制线段
            pcl::PointXYZ line_end = min_pt;//cluster_centroid + major_axis * line_length;

            // 添加中心线到可视化窗口
            viewer.addLine(max_pt, line_end, 1, 0, 0, "center_line", 0); // 红色线段
            viewer.setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 5, "center_line"); // 设置线宽
        }
        
        viewer.addPointCloud(cloud_cluster, "cloud_filtered");
    }

    // 可视化结果
    viewer.addCoordinateSystem(1.0);
    viewer.spin();

    return (0);
}
int getChairLeg()
{
    const std::string filename = "D:/temp/data/tutorials/table_scene_lms400.pcd";
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr table(new pcl::PointCloud<pcl::PointXYZ>);
    std::vector<pcl::PointIndices> cluster_indices;


    // 加载点云数据
    if (pcl::io::loadPCDFile<pcl::PointXYZ>(filename, *cloud) == -1)
    {
        std::cerr << "Failed to load PCD file!" << std::endl;
        return -1;
    }

    // 预处理：去除地面
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::SACSegmentation<pcl::PointXYZ> seg;
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_PLANE);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(1000);
    seg.setDistanceThreshold(0.01);
    seg.setInputCloud(cloud);
    seg.segment(*inliers, *coefficients);

    if (inliers->indices.size() == 0)
    {
        std::cout << "Could not estimate a planar model for the table" << std::endl;
        return -1;
    }

    // 从点云中移除地面
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    extract.setInputCloud(cloud);
    extract.setIndices(inliers);
    extract.setNegative(true);
    extract.filter(*table);

    // 聚类
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
    tree->setInputCloud(table);
    pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
    ec.setClusterTolerance(0.02);
    ec.setMinClusterSize(100);
    ec.setMaxClusterSize(10000);
    ec.setSearchMethod(tree);
    ec.setInputCloud(table);
    ec.extract(cluster_indices);


    // 可视化
    pcl::visualization::PCLVisualizer viewer("Table Leg Detection");
    viewer.setBackgroundColor(0, 0, 0);
//    viewer.addPointCloud(cloud, "original_cloud");
    viewer.addPointCloud(table, "table_cloud");


    // 处理每个聚类，尝试识别桌腿
    for (const auto& indices : cluster_indices)
    {
        pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
        for (const int idx : indices.indices)
            cluster->push_back((*table)[idx]);

        // 特征提取与分析
        pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
        feature_extractor.setInputCloud(cluster);
        feature_extractor.compute();

        float min_eigenvalue, median_eigenvalue, max_eigenvalue;
        Eigen::Vector3f min_eigenvector, median_eigenvector, max_eigenvector;
        feature_extractor.getEigenVectors(min_eigenvector, median_eigenvector, max_eigenvector);
        feature_extractor.getEigenValues(min_eigenvalue, median_eigenvalue, max_eigenvalue);

        Eigen::Vector3f center;
        feature_extractor.getMassCenter(center);

        // 确定桌腿的主轴方向（假设是最大特征值对应的向量）
        Eigen::Vector3f leg_direction = max_eigenvector;


        pcl::PointXYZ min_point = (*table)[indices.indices.front()];
        pcl::PointXYZ max_point = (*table)[indices.indices.front()];

        // 遍历该聚类中的所有点，寻找沿桌腿方向上的最远点和最近点
        for (const int idx : indices.indices)
        {
            pcl::PointXYZ current_point = (*table)[idx];
            // 直接使用点的坐标计算距离沿桌腿方向的投影
            float distance_along_leg = leg_direction.x() * (current_point.x - center.x()) +
                leg_direction.y() * (current_point.y - center.y()) +
                leg_direction.z() * (current_point.z - center.z());

            if (distance_along_leg < 0 && distance_along_leg <
                (min_point.x - center.x()) * leg_direction.x() +
                (min_point.y - center.y()) * leg_direction.y() +
                (min_point.z - center.z()) * leg_direction.z())
            {
                min_point = current_point;
            }
            else if (distance_along_leg > 0 && distance_along_leg >
                (max_point.x - center.x()) * leg_direction.x() +
                (max_point.y - center.y()) * leg_direction.y() +
                (max_point.z - center.z()) * leg_direction.z())
            {
                max_point = current_point;
            }
        }

                        
        // 桌腿长度估计
        float leg_length = (max_point.getVector3fMap() - min_point.getVector3fMap()).norm();

        // 桌腿中心线估计为两个端点的中点
        pcl::PointXYZ leg_center;
        leg_center.x = 0.5f * (min_point.x + max_point.x);
        leg_center.y = 0.5f * (min_point.y + max_point.y);
        leg_center.z = 0.5f * (min_point.z + max_point.z);

        pcl::PointXYZ leg_end;
        leg_end.x = leg_center.x + leg_direction.x() * leg_length;
        leg_end.y = leg_center.y + leg_direction.y() * leg_length;
        leg_end.z = leg_center.z + leg_direction.z() * leg_length;

        // 绘制桌腿中心线到可视化器
        viewer.addLine(leg_center, leg_end, 1.0, 0.0, 0.0, "leg_center_line", 0);

        //std::cout << "Cluster " << &indices - &cluster_indices.front() << ": Leg length estimated as " << leg_length << ", Center of the leg is at ("
        //    << leg_center.x << ", " << leg_center.y << ", " << leg_center.z << ")" << std::endl;
    
    }

    

    // 注意：这里未实现绘制桌腿中心线的代码，需要根据识别和拟合逻辑添加

    while (!viewer.wasStopped())
    {
        viewer.spinOnce();
    }
    return 0;
}

int getPipe()
{
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cylinder(new pcl::PointCloud<pcl::PointXYZ>);

    // 加载点云数据
    if (pcl::io::loadPCDFile<pcl::PointXYZ>("your_pointcloud.pcd", *cloud) == -1)
    {
        std::cerr << "Failed to load PCD file!" << std::endl;
        return -1;
    }

    // 预处理：下采样与去噪
    pcl::VoxelGrid<pcl::PointXYZ> vg;
    vg.setInputCloud(cloud);
    vg.setLeafSize(0.01f, 0.01f, 0.01f);
    vg.filter(*cloud_filtered);

    pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
    sor.setInputCloud(cloud_filtered);
    sor.setMeanK(50);
    sor.setStddevMulThresh(1.0);
    sor.filter(*cloud_filtered);

    // 使用RANSAC拟合圆柱模型
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::SACSegmentation<pcl::PointXYZ> seg;
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_CYLINDER);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setDistanceThreshold(0.01); // 适当调整拟合精度
    seg.setInputCloud(cloud_filtered);
    seg.segment(*inliers, *coefficients);

    if (inliers->indices.size() == 0)
    {
        std::cout << "No cylinder found." << std::endl;
        return 0;
    }

    // 提取圆柱模型
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    extract.setInputCloud(cloud_filtered);
    extract.setIndices(inliers);
    extract.setNegative(false);
    extract.filter(*cloud_cylinder);

    // 可视化结果
    pcl::visualization::PCLVisualizer viewer("Cylinder Detection");
//    viewer.addPointCloud(cloud_filtered, "cloud_filtered");
    viewer.addPointCloud(cloud_cylinder, "cloud_cylinder");
    viewer.addCoordinateSystem(1.0);
    viewer.spin();
    return 0;
}




std::tuple<pcl::ModelCoefficients::Ptr, pcl::PointCloud<pcl::PointXYZ>::Ptr, float>
    FitCylinder(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, float r)
{
    // 创建法向量估计对象
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
    // 设置搜索方式
    ne.setSearchMethod(tree);
    // 设置输入点云
    ne.setInputCloud(cloud);
    // 设置K近邻搜索点的个数
    ne.setKSearch(50);
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
    // 计算法向量，并将结果保存到cloud_normals中
    ne.compute(*cloud_normals);

    //圆柱体分割
    // 创建圆柱体分割对象
    pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
    // 设置输入点云：待分割点云 
    seg.setInputCloud(cloud);
    // 设置对估计的模型系数需要进行优化
    seg.setOptimizeCoefficients(true);
    // 设置分割模型为圆柱体模型
    seg.setModelType(pcl::SACMODEL_CYLINDER);
    // 设置采用RANSAC算法进行参数估计
    seg.setMethodType(pcl::SAC_RANSAC);
    // 设置表面法线权重系数
    seg.setNormalDistanceWeight(0.1);
    // 设置迭代的最大次数
    seg.setMaxIterations(6000);
    // 设置内点到模型距离的最大值
    seg.setDistanceThreshold(r/20);
    // 设置圆柱模型半径的范围
    seg.setRadiusLimits(r-0.5, r+0.5);
    // 设置输入法向量
    seg.setInputNormals(cloud_normals);
    // 保存分割结果
    pcl::PointIndices::Ptr inliers_cylinder(new pcl::PointIndices);
    // 保存圆柱体模型系数
    pcl::ModelCoefficients::Ptr coefficients_cylinder(new pcl::ModelCoefficients);
    // 执行分割，将分割结果的索引保存到inliers_cylinder中，同时存储模型系数coefficients_cylinder
    seg.segment(*inliers_cylinder, *coefficients_cylinder);
    qDebug() << "\n\t\t -圆柱体系数 -";
    qDebug() << "轴线一点坐标：(" <<
        coefficients_cylinder->values[0] << ", "
        << coefficients_cylinder->values[1] << ", "
        << coefficients_cylinder->values[2] << ")";

    qDebug() << "轴线方向向量：(" <<
        coefficients_cylinder->values[3] << ", "
        << coefficients_cylinder->values[4] << ", "
        << coefficients_cylinder->values[5] << ")";

    qDebug() << "圆柱体半径：" << coefficients_cylinder->values[6];

    //提取分割结果
    // 创建索引提取点对象
    pcl::ExtractIndices<pcl::PointXYZ> extract;
    // 设置输入点云：待分割点云
    extract.setInputCloud(cloud);
    // 设置内点索引
    extract.setIndices(inliers_cylinder);
    // 默认false，提取圆柱体内点；true，提取圆柱体外点
    extract.setNegative(false);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cylinder(new pcl::PointCloud<pcl::PointXYZ>());
    // 执行滤波，并将结果点云保存到cloud_cylinder中
    extract.filter(*cloud_cylinder);




    float x = 0, y = 0, z = 0;
    for (const auto& point : cloud_cylinder->points)
    {
        x += point.x;
        y += point.y;
        z += point.z;
    }

    coefficients_cylinder->values[0] = x/cloud_cylinder->points.size();
    coefficients_cylinder->values[1] = y / cloud_cylinder->points.size();
    coefficients_cylinder->values[2] = z / cloud_cylinder->points.size();


    Eigen::Vector3f cylinder_center(coefficients_cylinder->values[0], coefficients_cylinder->values[1], coefficients_cylinder->values[2]);
    Eigen::Vector3f cylinder_axis(coefficients_cylinder->values[3], coefficients_cylinder->values[4], coefficients_cylinder->values[5]);
    cylinder_axis.normalize();

    float min_height = std::numeric_limits<float>::max();
    float max_height = std::numeric_limits<float>::lowest();
    for (const auto& point : cloud_cylinder->points)
    {
        // 计算点到圆柱中心的向量
        Eigen::Vector3f point_vec = point.getVector3fMap() - cylinder_center.head<3>();

        // 计算点向量与圆柱轴向的点积，判断点与轴的投影距离
        float projection_length = point_vec.dot(cylinder_axis);

        // 更新高度的最小值和最大值
        min_height = std::min(min_height, projection_length);
        max_height = std::max(max_height, projection_length);
    }

    // 计算圆柱高度
     float height = std::abs(max_height - min_height);


    return { coefficients_cylinder,  cloud_cylinder, height };
}


void drawCylinderCenterLine(const pcl::ModelCoefficients::Ptr& coefficients, float h, pcl::visualization::PCLVisualizer& viewer)
{
    if (coefficients->values.size() != 7) // 圆柱模型应该有7个系数
    {
        std::cerr << "Invalid number of coefficients for cylinder model!" << std::endl;
        return;
    }

    Eigen::Vector3f c(coefficients->values[0], coefficients->values[1], coefficients->values[2]);
    Eigen::Vector3f cylinder_axis(coefficients->values[3], coefficients->values[4], coefficients->values[5]);

    // Normalize the axis for direction only
    cylinder_axis.normalize();

    // Define parameters for the central axis visualization
    const float line_length = h/2; // Length of the line
    pcl::PointXYZ start_point(c.x() + line_length * cylinder_axis.x(),
        c.y() + line_length * cylinder_axis.y(),
        c.z() + line_length * cylinder_axis.z());
    pcl::PointXYZ end_point(start_point.x - h * cylinder_axis.x(),
        start_point.y - h * cylinder_axis.y(),
        start_point.z - h * cylinder_axis.z());


    viewer.addLine(start_point, end_point, 1.0, 0.0, 0.0, "cylinder_center_line");
}

pcl::PointCloud<pcl::PointXYZ>::Ptr  createCylinder(double radius, double height, Eigen::Vector3f axis);
void costomerCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr basic_cloud_ptr, pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr);
int auto_render()
{
    // 创建一个可视化窗口
    pcl::visualization::PCLVisualizer viewer("3D Viewer");
    // 加载点云数据
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    //pcl::io::loadPCDFile("D:/temp/data/tutorials/table_scene_mug_stereo_textured.pcd", *cloud);
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr(new pcl::PointCloud<pcl::PointXYZRGB>);
    costomerCloud(cloud, point_cloud_ptr);
    Eigen::Vector3f axis(1,1,0);
    //cloud = createCylinder(1, 5, axis);

    // 获取点云边界框大小
    pcl::PointXYZ minPt, maxPt;
    pcl::getMinMax3D(*cloud, minPt, maxPt);
    // 计算点云中心位置和对角线长度
    Eigen::Vector3f center((maxPt.x + minPt.x) / 2, (maxPt.y + minPt.y) / 2, (maxPt.z + minPt.z) / 2);
    Eigen::Vector3f diff = maxPt.getVector3fMap() - minPt.getVector3fMap();
    float distance = diff.norm();
    // 将点云添加到可视化窗口中
    //viewer.addPointCloud<pcl::PointXYZ>(cloud, "sample cloud");
    // 设置点云的颜色和尺寸
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, "sample cloud");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
 



    pcl::ModelCoefficients::Ptr coefficients_cylinder;
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cylinder;
    float h = 0;
    std::tie(coefficients_cylinder, cloud_cylinder, h) = FitCylinder(cloud, 1.0);
    
    viewer.addPointCloud<pcl::PointXYZ>(cloud_cylinder, "sample cloud");
    drawCylinderCenterLine(coefficients_cylinder, h, viewer);


    // 设置相机位置和视角
    // (场景中心点的坐标, 摄像机到场景中心点的距离, 摄像机的方向向量)
    viewer.setCameraPosition(center(0), center(1), center(2) + distance, center(0), center(1), center(2), -0.7, -0.9, 0.2);
    // 显示可视化窗口
    viewer.spin();
    return 0;
}

void gen_test_cylinder()
{
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PCDReader reader;
    reader.read("D:/temp/data/tutorials/table_scene_lms400.pcd", *cloud);
    cout << "PointCloud has: " << cloud->points.size() << " data points." << endl;
    
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_ptr(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr point_cloud_ptr(new pcl::PointCloud<pcl::PointXYZRGB>);
    costomerCloud(cloud, point_cloud_ptr);

    for (const auto& pt : cloud_ptr->points) {
        cloud->points.push_back(pt);
    }

    pcl::PCDWriter writer;
    writer.write("test_cylinder.pcd", *cloud, false);
}

void testCylinder21()
{
    //------------------------------读取点云数据---------------------------------
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PCDReader reader;
    reader.read("D:/projects/pc3d/point-cloud-viewer/build/test_cylinder.pcd", *cloud);
    cout << "PointCloud has: " << cloud->points.size() << " data points." << endl;
    //--------------------------------直通滤波-----------------------------------
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PassThrough<pcl::PointXYZ > pass;
    pass.setInputCloud(cloud);
    pass.setFilterFieldName("z");//将Z轴不在（-1.5，0.11.5）范围内的点过滤掉
    pass.setFilterLimits(-0.1, 2.1);
    pass.filter(*cloud_filtered);//剩余的点储存在cloud_filtered中后续使用
    cout << "PointCloud after filtering has: " << cloud_filtered->points.size() << " data points." << endl;
    //--------------------------------计算法线-----------------------------------
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
    pcl::search::KdTree<pcl::PointXYZ >::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ >);
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_est;
    normal_est.setSearchMethod(tree);
    normal_est.setInputCloud(cloud_filtered);
    normal_est.setKSearch(50);
    normal_est.compute(*cloud_normals);
    //------------------------------创建分割对象---------------------------------
    pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
    pcl::ModelCoefficients::Ptr coefficients_plane(new pcl::ModelCoefficients), coefficients_cylinder(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers_plane(new pcl::PointIndices), inliers_cylinder(new pcl::PointIndices);
    // ------------------------点云分割，提取平面上的点--------------------------
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_NORMAL_PLANE);
    seg.setNormalDistanceWeight(0.1);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(100);
    seg.setDistanceThreshold(0.03);
    seg.setInputCloud(cloud_filtered);
    seg.setInputNormals(cloud_normals);
    seg.segment(*inliers_plane, *coefficients_plane);//获取平面模型系数和平面上的点
    cout << "Plane coefficients: " << *coefficients_plane << endl;
    //----------------------------------提取平面---------------------------------
    pcl::ExtractIndices<pcl::PointXYZ > extract;
    extract.setInputCloud(cloud_filtered);
    extract.setIndices(inliers_plane);
    extract.setNegative(false);
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZ >());
    extract.filter(*cloud_plane);
    cout << "PointCloud representing the planar component: " << cloud_plane->points.size() << " data points." << endl;
    
    //-------------------------------提取圆柱体模型------------------------------
    pcl::ExtractIndices<pcl::Normal> extract_normals;
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_filtered2(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PointCloud<pcl::Normal>::Ptr normals2(new pcl::PointCloud<pcl::Normal>);
    //获取平面以外的点和点的法线
    extract.setNegative(true);
    extract.filter(*cloud_filtered2);

    //为圆柱体分割创建分割对象，并设置参数
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cylinder;
    float h = 0;
    std::tie(coefficients_cylinder, cloud_cylinder, h) = FitCylinder(cloud_filtered, 1.0);

    //---------------可视化圆柱------------
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("sample cloud"));
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, "sample cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
        

    viewer->addPointCloud<pcl::PointXYZ>(cloud_cylinder, "sample cloud");
    drawCylinderCenterLine(coefficients_cylinder, h, *viewer.get());

    
    viewer->spin();
}

void main3();
int main4();

int getCylinder2()
{
    //main4();
    //getCylinder20();
    //gen_test_cylinder();
    //auto_render();
    testCylinder21();
    return 0;
    //------------------------------读取点云数据---------------------------------
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PCDReader reader;
    reader.read("D:/temp/data/tutorials/table_scene_mug_stereo_textured.pcd", *cloud);
    cout << "PointCloud has: " << cloud->points.size() << " data points." << endl;
    //--------------------------------直通滤波-----------------------------------
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PassThrough<pcl::PointXYZ > pass;
    pass.setInputCloud(cloud);
    pass.setFilterFieldName("z");//将Z轴不在（0，1.5）范围内的点过滤掉
    pass.setFilterLimits(0, 1.5);
    pass.filter(*cloud_filtered);//剩余的点储存在cloud_filtered中后续使用
    cout << "PointCloud after filtering has: " << cloud_filtered->points.size() << " data points." << endl;
    //--------------------------------计算法线-----------------------------------
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
    pcl::search::KdTree<pcl::PointXYZ >::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ >);
    pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_est;
    normal_est.setSearchMethod(tree);
    normal_est.setInputCloud(cloud_filtered);
    normal_est.setKSearch(50);
    normal_est.compute(*cloud_normals);
    //------------------------------创建分割对象---------------------------------
    pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
    pcl::ModelCoefficients::Ptr coefficients_plane(new pcl::ModelCoefficients), coefficients_cylinder(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers_plane(new pcl::PointIndices), inliers_cylinder(new pcl::PointIndices);
    // ------------------------点云分割，提取平面上的点--------------------------
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_NORMAL_PLANE);
    seg.setNormalDistanceWeight(0.1);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(100);
    seg.setDistanceThreshold(0.03);
    seg.setInputCloud(cloud_filtered);
    seg.setInputNormals(cloud_normals);
    seg.segment(*inliers_plane, *coefficients_plane);//获取平面模型系数和平面上的点
    cout << "Plane coefficients: " << *coefficients_plane << endl;
    //----------------------------------提取平面---------------------------------
    pcl::ExtractIndices<pcl::PointXYZ > extract;
    extract.setInputCloud(cloud_filtered);
    extract.setIndices(inliers_plane);
    extract.setNegative(false);
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZ >());
    extract.filter(*cloud_plane);
    cout << "PointCloud representing the planar component: " << cloud_plane->points.size() << " data points." << endl;
    //-----------------------------存储点云到输出文件----------------------------
    pcl::PCDWriter writer;
    writer.write("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);
    //-------------------------------提取圆柱体模型------------------------------
    pcl::ExtractIndices<pcl::Normal> extract_normals;
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_filtered2(new pcl::PointCloud<pcl::PointXYZ >);
    pcl::PointCloud<pcl::Normal>::Ptr normals2(new pcl::PointCloud<pcl::Normal>);
    //获取平面以外的点和点的法线
    extract.setNegative(true);
    extract.filter(*cloud_filtered2);
    extract_normals.setNegative(true);
    extract_normals.setInputCloud(cloud_normals);
    extract_normals.setIndices(inliers_plane);
    extract_normals.filter(*normals2);
    //为圆柱体分割创建分割对象，并设置参数
    seg.setOptimizeCoefficients(true);        //设置对估计的模型系数需要进行优化
    seg.setModelType(pcl::SACMODEL_CYLINDER); //设置分割模型为圆柱型
    seg.setMethodType(pcl::SAC_RANSAC);       //设置采用RANSAC作为算法的参数估计方法
    seg.setNormalDistanceWeight(0.1);         //设置表面法线权重系数
    seg.setMaxIterations(5000);               //设置迭代的最大次数
    seg.setDistanceThreshold(0.05);           //设置内点到模型的距离允许最大值 
    seg.setRadiusLimits(0, 0.1);              //设置估计出圆柱模型的半径范围
    seg.setInputCloud(cloud_filtered2);
    seg.setInputNormals(normals2);
    //获取圆柱模型系数和圆柱上的点
    seg.segment(*inliers_cylinder, *coefficients_cylinder);
    cout << "Cylinder coefficients: " << *coefficients_cylinder << endl;
    //-----------------------------存储点云到输出文件----------------------------
    extract.setInputCloud(cloud_filtered2);
    extract.setIndices(inliers_cylinder);
    extract.setNegative(false);
    pcl::PointCloud<pcl::PointXYZ >::Ptr cloud_cylinder(new pcl::PointCloud<pcl::PointXYZ >);
    extract.filter(*cloud_cylinder);
    if (cloud_cylinder->points.empty())
        cout << "Can't find the cylindrical component." << endl;
    else
    {
        cout << "PointCloud representing the cylindrical component: " << cloud_cylinder->points.size() << " data points." << endl;
        writer.write("table_scene_mug_stereo_textured_cylinder.pcd", *cloud_cylinder, false);
    }
    //---------------可视化，从左到右依次是原始点云，平面，圆柱------------
    boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("segment display"));
    //原始点云
    int v1(0);
    viewer->createViewPort(0.0, 0.0, 0.33, 1, v1);
    viewer->setBackgroundColor(0, 255, 0, v1);
    viewer->addPointCloud(cloud, "cloud", v1);
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, "cloud");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud");
    //平面
    int v2(0);
    viewer->createViewPort(0.33, 0.0, 0.66, 1, v2);
    viewer->setBackgroundColor(0, 0, 255, v2);
    viewer->addPointCloud(cloud_plane, "cloud_plane", v2);
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, "cloud_plane");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud_plane");
    //圆柱
    int v3(0);
    viewer->createViewPort(0.66, 0.0, 1, 1, v3);
    viewer->setBackgroundColor(0, 255, 0, v3);
    viewer->addPointCloud(cloud_cylinder, "cloud_cylinder", v3);
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 0, 0, "cloud_cylinder");
    viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud_cylinder");


    // 计算边界框
    pcl::PointXYZ min_pt, max_pt;
    pcl::getMinMax3D(*cloud_cylinder, min_pt, max_pt);

    // 定义立方体的边
    std::vector<pcl::PointXYZ> cubeVertices = {
        pcl::PointXYZ(min_pt.x, min_pt.y, min_pt.z), // 0
        pcl::PointXYZ(max_pt.x, min_pt.y, min_pt.z), // 1
        pcl::PointXYZ(max_pt.x, max_pt.y, min_pt.z), // 2
        pcl::PointXYZ(min_pt.x, max_pt.y, min_pt.z), // 3
        pcl::PointXYZ(min_pt.x, min_pt.y, max_pt.z), // 4
        pcl::PointXYZ(max_pt.x, min_pt.y, max_pt.z), // 5
        pcl::PointXYZ(max_pt.x, max_pt.y, max_pt.z), // 6
        pcl::PointXYZ(min_pt.x, max_pt.y, max_pt.z)  // 7
    };

    // 添加立方体的边到viewer
    std::vector<std::pair<int, int>> edges = {
        {0, 1}, {1, 2}, {2, 3}, {3, 0}, // 底面
        {4, 5}, {5, 6}, {6, 7}, {7, 4}, // 顶面
        {0, 4}, {1, 5}, {2, 6}, {3, 7}  // 连接底面与顶面
    };

    for (const auto& edge : edges) {
        viewer->addLine(cubeVertices[edge.first], cubeVertices[edge.second], "edge_" + std::to_string(edge.first) + "_" + std::to_string(edge.second), v3);
    }



    // Assuming coefficients_cylinder->values = [cx, cy, cz, dx, dy, dz, radius]
    Eigen::Vector3f cylinder_center(coefficients_cylinder->values[0], coefficients_cylinder->values[1], coefficients_cylinder->values[2]);
    Eigen::Vector3f cylinder_axis(coefficients_cylinder->values[3], coefficients_cylinder->values[4], coefficients_cylinder->values[5]);

    // Normalize the axis for direction only
    cylinder_axis.normalize();
        
    // Define parameters for the central axis visualization
    const float line_length = 13.0f * coefficients_cylinder->values[6]; // Length of the line
    pcl::PointXYZ start_point(cylinder_center.x(), cylinder_center.y(), cylinder_center.z());
    pcl::PointXYZ end_point(start_point.x + line_length * cylinder_axis.x(),
        start_point.y + line_length * cylinder_axis.y(),
        start_point.z + line_length * cylinder_axis.z());

    // Add the central axis as a line to the viewer
    viewer->addLine(start_point, end_point, 1, 0, 0, "cylinder_axis", v3); // Red color to match the cylinder
    viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 5, "cylinder_axis", v3); // Set line width within the viewport

    auto len = cloud_cylinder->points.size();
    auto p1 = cloud_cylinder->points[0];
    auto p2 = cloud_cylinder->points[len/4];
    auto p3 = cloud_cylinder->points[len/8];
    viewer->addLine(p1, p2, 1, 1, 1, "cylinder_axis1", v3); // Red color to match the cylinder
    viewer->addLine(p2, p3, 1, 1, 1, "cylinder_axis2", v3); // Red color to match the cylinder
    viewer->addLine(p3, p1, 1, 1, 1, "cylinder_axis3", v3); // Red color to match the cylinder


    while (!viewer->wasStopped())
    {
        viewer->spinOnce(100);
        boost::this_thread::sleep(boost::posix_time::microseconds(10000));
    }


    return 0;
}

int getCylinder20()
{
    // All the objects needed
    pcl::PCDReader reader;
    pcl::PassThrough<PointT> pass;
    pcl::NormalEstimation<PointT, pcl::Normal> ne;
    pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg;
    pcl::PCDWriter writer;
    pcl::ExtractIndices<PointT> extract;
    pcl::ExtractIndices<pcl::Normal> extract_normals;
    pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>());

    // Datasets
    pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>);
    pcl::PointCloud<PointT>::Ptr cloud_filtered(new pcl::PointCloud<PointT>);
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
    pcl::PointCloud<PointT>::Ptr cloud_filtered2(new pcl::PointCloud<PointT>);
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2(new pcl::PointCloud<pcl::Normal>);
    pcl::ModelCoefficients::Ptr coefficients_plane(new pcl::ModelCoefficients), coefficients_cylinder(new pcl::ModelCoefficients);
    pcl::PointIndices::Ptr inliers_plane(new pcl::PointIndices), inliers_cylinder(new pcl::PointIndices);

    // Read in the cloud data
    reader.read("D:/temp/data/tutorials/table_scene_mug_stereo_textured.pcd", *cloud);
    std::cerr << "PointCloud has: " << cloud->size() << " data points." << std::endl;

    // Build a passthrough filter to remove spurious NaNs and scene background
    pass.setInputCloud(cloud);
    pass.setFilterFieldName("z");
    pass.setFilterLimits(0, 1.5);
    pass.filter(*cloud_filtered);
    std::cerr << "PointCloud after filtering has: " << cloud_filtered->size() << " data points." << std::endl;

    // Estimate point normals
    ne.setSearchMethod(tree);
    ne.setInputCloud(cloud_filtered);
    ne.setKSearch(50);
    ne.compute(*cloud_normals);

    // Create the segmentation object for the planar model and set all the parameters
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_NORMAL_PLANE);
    seg.setNormalDistanceWeight(0.1);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(100);
    seg.setDistanceThreshold(0.03);
    seg.setInputCloud(cloud_filtered);
    seg.setInputNormals(cloud_normals);
    // Obtain the plane inliers and coefficients
    seg.segment(*inliers_plane, *coefficients_plane);
    std::cerr << "Plane coefficients: " << *coefficients_plane << std::endl;

    // Extract the planar inliers from the input cloud
    extract.setInputCloud(cloud_filtered);
    extract.setIndices(inliers_plane);
    extract.setNegative(false);

    // Write the planar inliers to disk
    pcl::PointCloud<PointT>::Ptr cloud_plane(new pcl::PointCloud<PointT>());
    extract.filter(*cloud_plane);
    std::cerr << "PointCloud representing the planar component: " << cloud_plane->size() << " data points." << std::endl;
    //writer.write("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);

    // Remove the planar inliers, extract the rest
    extract.setNegative(true);
    extract.filter(*cloud_filtered2);
    extract_normals.setNegative(true);
    extract_normals.setInputCloud(cloud_normals);
    extract_normals.setIndices(inliers_plane);
    extract_normals.filter(*cloud_normals2);

    // Create the segmentation object for cylinder segmentation and set all the parameters
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_CYLINDER);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setNormalDistanceWeight(0.1);
    seg.setMaxIterations(10000);
    seg.setDistanceThreshold(0.05);
    seg.setRadiusLimits(0, 0.1);
    seg.setInputCloud(cloud_filtered2);
    seg.setInputNormals(cloud_normals2);

    // Obtain the cylinder inliers and coefficients
    seg.segment(*inliers_cylinder, *coefficients_cylinder);
    std::cerr << "Cylinder coefficients: " << *coefficients_cylinder << std::endl;

    // Write the cylinder inliers to disk
    extract.setInputCloud(cloud_filtered2);
    extract.setIndices(inliers_cylinder);
    extract.setNegative(false);
    pcl::PointCloud<PointT>::Ptr cloud_cylinder(new pcl::PointCloud<PointT>());
    extract.filter(*cloud_cylinder);
    if (cloud_cylinder->points.empty())
        std::cerr << "Can't find the cylindrical component." << std::endl;
    else
    {
        std::cerr << "PointCloud representing the cylindrical component: " << cloud_cylinder->size() << " data points." << std::endl;
        //writer.write("table_scene_mug_stereo_textured_cylinder.pcd", *cloud_cylinder, false);
    }

    {
        float cx = coefficients_cylinder->values[0];
        float cy = coefficients_cylinder->values[1];
        float cz = coefficients_cylinder->values[2];
        float ux = coefficients_cylinder->values[3];
        float uy = coefficients_cylinder->values[4];
        float uz = coefficients_cylinder->values[5];

        pcl::PointXYZ centerPt(cx, cy, cz);

        // 定义直线的两端点，这里我们让直线长度超出圆柱的实际长度以确保可见，你可以根据需要调整
        const float line_length = 1.5 * sqrt(ux * ux + uy * uy + uz * uz); // 使用圆柱直径的1.5倍作为线长是一个常见的选择
        pcl::PointXYZ line_start(cx - 0.5 * line_length * ux, cy - 0.5 * line_length * uy, cz - 0.5 * line_length * uz);
        pcl::PointXYZ line_end(cx + 0.5 * line_length * ux, cy + 0.5 * line_length * uy, cz + 0.5 * line_length * uz);

        // 初始化可视化器
        pcl::visualization::PCLVisualizer viewer("Cylinder Detection");

        // 添加点云
        viewer.addPointCloud(cloud_cylinder, "cloud_cylinder");
        viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "cloud_cylinder"); // 设置颜色为绿色

        // 添加圆柱中心线
        viewer.addLine(line_start, centerPt, 1.0, 0.0, 0.0, "cylinder_line", 0); // 红色中心线
        
        // 设置背景颜色等其他可视化选项
        viewer.setBackgroundColor(0, 0, 0);
        viewer.setCameraPosition(cx, cy, cz, 0, 0, 1, 10); // 根据需要调整相机位置
    

        auto len = cloud_cylinder->points.size();
        auto p1 = cloud_cylinder->points[0];
        auto p2 = cloud_cylinder->points[len / 4];
        auto p3 = cloud_cylinder->points[len / 8];
        viewer.addLine(p1, p2, 1, 1, 1, "cylinder_axis1"); // Red color to match the cylinder
        viewer.addLine(p2, p3, 1, 1, 1, "cylinder_axis2"); // Red color to match the cylinder
        viewer.addLine(p3, p1, 1, 1, 1, "cylinder_axis3"); // Red color to match the cylinder


        // 开始循环显示
        while (!viewer.wasStopped())
        {
            viewer.spinOnce(100);
            boost::this_thread::sleep(boost::posix_time::microseconds(100000));
        }

    }



    return (0);
}



void
PCLVisualizer::on_actionExportLog_triggered()
{

  QString filename = QFileDialog::getSaveFileName(
    this, tr("Save Log"), "/home/", tr("Log(*.txt)"));
  PCL_INFO("File chosen: %s\n", filename.toStdString().c_str());

  QFile file;
  file.setFileName(filename);
  QByteArray data;

  QString log;
  for (auto it = logList.begin(); it != logList.end(); ++it) {
    log.append(*it + "\n");
  }
  if (file.open(QIODevice::WriteOnly)) {
    QByteArray res2 = log.toUtf8(); // toLatin1()转为QByteArray
    file.write(res2);
    file.close();
  }
}

void
PCLVisualizer::on_actionRedo_triggered()
{
  ++iterations;
  //  if (iterations < 3) {
  //    openProgressDlg(300);
  //  }

  // qDebug() << "iterations: "<< iterations << endl;
  // TODO 暂时在这里做点云配准显示

  ICP_aligin(icp, cloud_in, cloud_RE);

  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h(
    cloud_in, 20, 20, 180);
  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h(
    cloud_tr, 250, 80, 0);
  pcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h(
    cloud_RE, 180, 20, 20);

  viewer_->updatePointCloud(cloud_RE, cloud_icp_color_h, "cloud2");
  viewer_->updatePointCloud(cloud_in, cloud_in_color_h, "cloud");
  viewer_->resetCamera();
  ui->qvtkWidget->update();
}

void
PCLVisualizer::on_actionquit_triggered()
{}

double
obb(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) //点云OBB有向包围盒
{
  pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
  feature_extractor.setInputCloud(cloud);
  feature_extractor.compute();

  std::vector<float> moment_of_inertia;
  std::vector<float> eccentricity;
  pcl::PointXYZ min_point_OBB;
  pcl::PointXYZ max_point_OBB;
  pcl::PointXYZ position_OBB;
  Eigen::Matrix3f rotational_matrix_OBB;
  float major_value, middle_value, minor_value;
  Eigen::Vector3f major_vector, middle_vector, minor_vector;
  Eigen::Vector3f mass_center;

  feature_extractor.getMomentOfInertia(moment_of_inertia);
  feature_extractor.getEccentricity(eccentricity);
  feature_extractor.getOBB(
    min_point_OBB, max_point_OBB, position_OBB, rotational_matrix_OBB);
  feature_extractor.getEigenValues(major_value, middle_value, minor_value);
  feature_extractor.getEigenVectors(major_vector, middle_vector, minor_vector);
  feature_extractor.getMassCenter(mass_center);

  return (max_point_OBB.x - min_point_OBB.x) *
         (max_point_OBB.y - min_point_OBB.y) *
         (max_point_OBB.z - min_point_OBB.z);

  ////绘制OBB包围盒
  // pcl::visualization::PCLVisualizer::Ptr viewer(new
  // pcl::visualization::PCLVisualizer("3D Viewer"));
  // viewer->setBackgroundColor(0, 0, 0);
  ////viewer->addCoordinateSystem(1.0);

  // pcl::visualization::PointCloudColorHandlerRandom<pcl::PointXYZ>
  // RandomColor(cloud);//设置随机颜色
  // viewer->addPointCloud<pcl::PointXYZ>(cloud, RandomColor, "points");
  // viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
  // 3, "points");

  // Eigen::Vector3f position(position_OBB.x, position_OBB.y, position_OBB.z);
  // Eigen::Quaternionf quat(rotational_matrix_OBB);
  // viewer->addCube(position, quat, max_point_OBB.x - min_point_OBB.x,
  // max_point_OBB.y - min_point_OBB.y, max_point_OBB.z - min_point_OBB.z,
  // "OBB");
  // viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION,
  // pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "OBB");

  // pcl::PointXYZ center(mass_center(0), mass_center(1), mass_center(2));
  // pcl::PointXYZ x_axis(major_vector(0) + mass_center(0), major_vector(1) +
  // mass_center(1), major_vector(2) + mass_center(2)); pcl::PointXYZ
  // y_axis(middle_vector(0) + mass_center(0), middle_vector(1) +
  // mass_center(1), middle_vector(2) + mass_center(2)); pcl::PointXYZ
  // z_axis(minor_vector(0) + mass_center(0), minor_vector(1) + mass_center(1),
  // minor_vector(2) + mass_center(2)); viewer->addLine(center, x_axis, 1.0f,
  // 0.0f, 0.0f, "major eigen vector");//主成分 viewer->addLine(center, y_axis,
  // 0.0f, 1.0f, 0.0f, "middle eigen vector"); viewer->addLine(center, z_axis,
  // 0.0f, 0.0f, 1.0f, "minor eigen vector");

  // std::cout << mass_center << std::endl;//中心点
  // std::cout << rotational_matrix_OBB << std::endl;//矩阵

  // while (!viewer->wasStopped())
  //{
  //	viewer->spinOnce(100);
  //}
}

double
aabb(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) //点云AABB包围盒
{
  pcl::MomentOfInertiaEstimation<pcl::PointXYZ> feature_extractor;
  feature_extractor.setInputCloud(cloud);
  feature_extractor.compute();

  std::vector<float> moment_of_inertia;
  std::vector<float> eccentricity;

  pcl::PointXYZ min_point_AABB; // AABB包围盒
  pcl::PointXYZ max_point_AABB;

  Eigen::Vector3f major_vector, middle_vector, minor_vector;
  Eigen::Vector3f mass_center;

  feature_extractor.getMomentOfInertia(moment_of_inertia);
  feature_extractor.getEccentricity(eccentricity);
  feature_extractor.getAABB(min_point_AABB, max_point_AABB);
  feature_extractor.getEigenVectors(major_vector, middle_vector, minor_vector);
  feature_extractor.getMassCenter(mass_center);

  return (max_point_AABB.x - min_point_AABB.x) *
         (max_point_AABB.y - min_point_AABB.y) *
         (max_point_AABB.z - min_point_AABB.z);
  ////绘制AABB包围盒
  // pcl::visualization::PCLVisualizer::Ptr viewer(new
  // pcl::visualization::PCLVisualizer("3D Viewer"));
  // viewer->setBackgroundColor(0, 0, 0);
  ////viewer->addCoordinateSystem(1.0);

  // pcl::visualization::PointCloudColorHandlerRandom<pcl::PointXYZ>
  // RandomColor(cloud);//设置随机颜色
  // viewer->addPointCloud<pcl::PointXYZ>(cloud, RandomColor, "points");
  // viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
  // 3, "points");

  // viewer->addCube(min_point_AABB.x, max_point_AABB.x, min_point_AABB.y,
  // max_point_AABB.y, min_point_AABB.z, max_point_AABB.z, 1.0, 1.0, 0.0,
  // "AABB");
  // viewer->setShapeRenderingProperties(pcl::visualization::PCL_VISUALIZER_REPRESENTATION,
  // pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "AABB");

  // pcl::PointXYZ center(mass_center(0), mass_center(1), mass_center(2));
  // pcl::PointXYZ x_axis(major_vector(0) + mass_center(0), major_vector(1) +
  // mass_center(1), major_vector(2) + mass_center(2)); pcl::PointXYZ
  // y_axis(middle_vector(0) + mass_center(0), middle_vector(1) +
  // mass_center(1), middle_vector(2) + mass_center(2)); pcl::PointXYZ
  // z_axis(minor_vector(0) + mass_center(0), minor_vector(1) + mass_center(1),
  // minor_vector(2) + mass_center(2)); viewer->addLine(center, x_axis, 1.0f,
  // 0.0f, 0.0f, "major eigen vector"); viewer->addLine(center, y_axis,
  // 0.0f, 1.0f, 0.0f, "middle eigen vector"); viewer->addLine(center, z_axis,
  // 0.0f, 0.0f, 1.0f, "minor eigen vector");

  // while (!viewer->wasStopped())
  //{
  //	viewer->spinOnce(100);
  //}
}

void
PCLVisualizer::on_actiongetAllGeo_triggered()
{
  vtkSmartPointer<vtkPLYReader> reader = vtkSmartPointer<vtkPLYReader>::New();
  reader->SetFileName(filePathWithName.toUtf8());
  reader->Update();
  vtkSmartPointer<vtkTriangleFilter> tri =
    vtkSmartPointer<vtkTriangleFilter>::New();
  tri->SetInputData(reader->GetOutput());
  tri->Update();
  vtkSmartPointer<vtkMassProperties> poly =
    vtkSmartPointer<vtkMassProperties>::New();
  poly->SetInputData(tri->GetOutput());
  poly->Update();

  double pV = poly->GetVolumeProjected();
  double pX = poly->GetVolumeX();
  double pY = poly->GetVolumeY();
  double pZ = poly->GetVolumeZ();
  double vol = poly->GetVolume();          //体积
  double area = poly->GetSurfaceArea();    //表面积
  double maxArea = poly->GetMaxCellArea(); //最大单元面积
  double minArea = poly->GetMinCellArea(); //最小单元面积

  pcl::PLYReader PLY_reader;
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
  PLY_reader.read<pcl::PointXYZ>(filePathWithName.toStdString(), *cloud);

  double vol_aabb = aabb(cloud);
  double vol_obb = obb(cloud);

  ui->lineEdit_AABB->setText(QString::number(vol_aabb));
  ui->lineEdit_OBB->setText(QString::number(vol_obb));
  ui->lineEdit_area->setText(QString::number(area));
  ui->lineEdit_maxUnitAera->setText(QString::number(maxArea));
  ui->lineEdit_minUnitAera->setText(QString::number(minArea));
  ui->lineEdit_vol->setText(QString::number(vol));

  // QMessageBox::information(
  //  this, "几何属性提取成功", "表面积、体积等计算完成", "确定");

  cout << "vol: " << vol << endl;
  cout << "area: " << area << endl;
  cout << "maxArea: " << maxArea << endl;
  cout << "minArea: " << minArea << endl;
  cout << "pV: " << pV << endl;
  cout << "pX: " << pX << endl;
  cout << "pY: " << pY << endl;
  cout << "pZ: " << pZ << endl;
}

void
PCLVisualizer::on_actionplaneSeg_triggered()
{
  // ----------------------------加载点云-----------------------------
  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
  pcl::PCDWriter writer;
  //  if (pcl::io::loadPCDFile<pcl::PointXYZ>(
  //        "E:\\PCLProject\\pcl-project\\pcl_segmentation\\plane\\cmake_"
  //        "bin\\table_scene_lms400_downSampling.pcd",
  //        *cloud) == -1) {
  //    PCL_ERROR("读取源标点云失败 \n");
  //  }
  *cloud = *cloud_;
  cout << "从点云中读取 " << cloud->size() << " 个点" << endl;

  //第一步：定义输入的原始数据以及分割获得的点、平面系数coefficients、存储内点的索引集合对象inliers
  pcl::PointCloud<pcl::PointXYZ>::Ptr planar_segment(
    new pcl::PointCloud<pcl::PointXYZ>); //创建分割对象
  pcl::ModelCoefficients::Ptr coefficients(
    new pcl::ModelCoefficients);                         //模型系数
  pcl::PointIndices::Ptr inliers(new pcl::PointIndices); //索引列表
  pcl::SACSegmentation<pcl::PointXYZ> seg;               //分割对象

  pcl::ExtractIndices<pcl::PointXYZ> extract; //提取器

  int n_piece = 2; //需要探测的面的个数

  //第二步：加载原始点云到可视化窗口

  //    viewer->setBackgroundColor(0, 0, 0.2);
  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> single_color(
    cloud, 255, 255, 255);
  viewer_->updatePointCloud<pcl::PointXYZ>(cloud, single_color, "cloud");
  // viewer_->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
  // 1, "sample"); 第三步：使用RANSAC获取点数最多的面
  for (int i = 0; i < n_piece; i++) {
    seg.setOptimizeCoefficients(true); //使用内部点重新估算模型参数
    seg.setModelType(pcl::SACMODEL_PLANE); //设置模型类型
    seg.setMethodType(pcl::SAC_RANSAC); //设置随机采样一致性方法类型
    seg.setDistanceThreshold(
      0.01); //设定距离阀值，距离阀值决定了点被认为是局内点是必须满足的条件
    seg.setInputCloud(cloud);
    seg.segment(*inliers, *coefficients);

    extract.setInputCloud(cloud);
    extract.setIndices(inliers);
    extract.setNegative(false);
    //提取探测出来的平面
    extract.filter(*planar_segment);
    // planar_segment为该次探测出来的面片，可以单独进行保存，此处省略

    //剔除探测出的平面，在剩余点中继续探测平面
    extract.setNegative(true);
    extract.filter(*cloud);

    int R;
    int G;
    int B;
    if (i == 0) {
      R = 0;
      G = 255;
      B = 0;
    } else {
      R = 255;
      G = 0;
      B = 0;
    }
    std::stringstream ss;
    ss << "planar_segment_" << (i + 1) << ".pcd";
    std::string str;
    ss >> str;
    writer.write<pcl::PointXYZ>(str, *planar_segment, false);

    pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> plane_color(
      planar_segment, R, G, B);
    viewer_->addPointCloud<pcl::PointXYZ>(planar_segment, plane_color, str);
    viewer_->setPointCloudRenderingProperties(
      pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, str);

    viewer_->resetCamera();
    ui->qvtkWidget->update();
  }

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[PCV 主窗口] " + "Point Cloud Plane Segmentation Done.";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  //--------------------LOG--------------------------
  logStr = "[" + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") +
           "] " + "[PCV 主窗口] " + "PCV has found 2 planes.";
  logList.push_back(logStr);
  ui->logList->addItem(logStr);
  //--------------------LOG--------------------------

  // QString info = "搜索到两个平面，是否进行平面分割？";
  QMessageBox::information(
    this, "平面分割成功", "搜索到2个平面，是否进行提取？", "确定", "取消");
}

//显示PLY文件
void
PCLVisualizer::on_actionarea_triggered()
{
  loadPLYFile();
  // pcl::PolygonMesh mesh;
  // pcl::io::loadPLYFile(filePathWithName.toStdString(), mesh);
  // viewer_->removePointCloud("cloud");
  // viewer_->addPolygonMesh(mesh, "my");
  // viewer_->resetCamera();
  // ui->qvtkWidget->update();
}

void
PCLVisualizer::on_actionvol_triggered()
{
  QMessageBox::information(
    this, "几何属性提取成功", "表面积、体积等计算完成", "确定");
}
